java單元測試:unit testing best practices

weixin_33785972發表於2019-01-11

常用技術:stub, mock
常用工具:Junit, TestNG; Jmockit, Powermock, Mockito

單元測試是如何發現bug的

假設有下面的類,可以判斷一個數是不是質數(只有1和本身兩個因數的數):

public class PrimeDecider {
     final int number;
     
        public PrimeDecider(int number) {
            this.number = number;
        }
     
        public boolean isPrime() {
            for (int n = 2; n * n <number; n++) {
                if (number % n == 0) {
                    return false;
                }
            }
            return true;
        }
}

對應的單元測試

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class PrimDeciderTest {
     @Test
        public void sample_2_IsPrime() {
            PrimeDecider decider = new PrimeDecider(2);
            boolean itIsPrime = decider.isPrime();
            assertTrue(itIsPrime);
        }
     
        @Test
        public void sample_17_IsPrime() {
            PrimeDecider decider = new PrimeDecider(17);
            boolean itIsPrime = decider.isPrime();
            assertTrue(itIsPrime);
        }
     
        @Test
        public void sample_10_IsNotPrime() {
            PrimeDecider decider = new PrimeDecider(10);
            boolean itIsPrime = decider.isPrime();
            assertFalse(itIsPrime);
        }
        
        @Test
        public void sample_9_IsNotPrime() {
            PrimeDecider decider = new PrimeDecider(9);
            boolean itIsPrime = decider.isPrime();
            assertFalse(itIsPrime);
        }
}

執行結果發現sample_9_IsNotPrime是失敗的,原因是PrimeDecider程式碼有BUG,少了一個等號, 修復的方法是修改判斷質數的迴圈那個算數:

public boolean isPrime() {
            for (int n = 2; n * n <=number; n++) {
                if (number % n == 0) {
                    return false;
                }
            }
            return true;
        }

哪些需要單元測試?那些不需要單元測試

需要單元測試:

  • Core code that is accessed by a lot of other modules
  • Code that seems to gather a lot of bugs
  • Code that changes by multiple different developers (often to accommodate new requirements)

不需要單元測試(同時需要考慮是不是需要用integration test來覆蓋)

  1. Other framework libraries (you should assume they work correctly)
  2. The database (you should assume it works correctly when it is available)
  3. Other external resources (again you assume they work correctly when available)
  4. Really trivial code (like getters and setters for example)
  5. Code that has non deterministic results (Think Thread order or random numbers)
  6. Code that deals only with UI (e.g. Swing toolkit, Wicket)
  7. Don’t unit test I/O. I/O is for integrations. Use integration tests, instead.

Stub VS. Mock

區別

13918377-e55f315be8c37aab.png
image.png

樁模組用來模擬被測試的模組所呼叫的模組。驅動模組用來呼叫被測模組,模擬使用者行為。


13918377-aceb918da0413963.png
image.png

用例子演示stub和mock的區別

有一個介面:

public interface Service {
  // Get real data from database for example.
  List findLanguages();
}

有個依賴介面的類,其中有如下程式碼

public CallService(Service service) {
  this.service = service;
}

public List findLanguagesWithA() {
  List languages = new ArrayList();
  for (String s : service.findLanguages()) {
    if (s.contains("a"))
      languages.add(s);
  }
  return languages;
}
使用stub測試

打了一個樁(stub), 隔離對Service 介面的依賴:

@Test
public void whenCallServiceIsStubbed() {
  CallService service = new CallService(new StubCallService());
  assertTrue(service.findLanguagesWithA().size() == 1);
  assertTrue(service.findLanguagesWithA().get(0).equals("Java"));
}

class StubCallService implements Service {
  public List findLanguages() {
    return Arrays.asList(
        new String[] { "Groovy", "Clojure", "Java"});
  }
}
使用mock測試

使用EasyMock對service介面進行mock:

@Test
public void whenCallServiceIsMocked() {
  Service mock = createControl().createMock(Service.class);
  CallService service = new CallService(mock);

  expect(mock.findLanguages()).andReturn(Arrays.asList(
      new String[] { "Groovy", "Clojure", "Java"}));
  replay(mock);

  List languages = service.findLanguagesWithA();
  assertTrue(languages.size() == 1);
  assertTrue(languages.get(0).equals("Java"));
  verify(mock);
}

https://dzone.com/articles/testing-without-a-mock-framework

mock工具比較

來自於# JMockit的比較:

13918377-54ba468d5208e1a0.png
image.png

13918377-6b7306a469e4832d.png
image.png

參考stackoverflow的說法, 目前比較流行的是JMockit、 PowerMock, 其次是 Mockito.

References

http://callistaenterprise.se/blogg/teknik/2010/11/12/stubs-n-mocks/
https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub
https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub/17810004#17810004
Test Double - Martin Fowler
Test Double - xUnit Patterns
Mocks Aren't Stubs - Martin Fowler
Command Query Separation - Martin Fowler
https://www.javaworld.com/article/2074508/core-java/mocks-and-stubs---understanding-test-doubles-with-mockito.html
https://www.hostettler.net/blog/2014/05/18/fakes-stubs-dummy-mocks-doubles-and-all-that/
http://www.cnblogs.com/TankXiao/archive/2012/03/06/2366073.html#silimar
https://martinfowler.com/articles/mocksArentStubs.html
http://web.cs.iastate.edu/~smkautz/cs227f14/labs/lab5/page08.html
https://javax0.wordpress.com/2015/02/04/do-not-unit-test-bugs/
https://dzone.com/articles/unit-tests-dont-find-bugs
http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/
https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a
https://zeroturnaround.com/rebellabs/dont-test-blindly-the-right-methods-for-unit-testing-your-java-apps/
https://softwareengineering.stackexchange.com/questions/306277/testing-using-mocking-must-i-mock-all-dependencies-too
https://blog.codecentric.de/en/2017/07/mocks-real-thing-tips-better-unit-testing/

相關文章