1. 程式人生 > >Mockito 簡明教程

Mockito 簡明教程

spa ace was imp comment 處理 允許 ng- 相等

原文同步至 http://waylau.com/mockito-quick-start/

Mock 測試是單元測試的重要方法之一。本文介紹了基於 Java 語言的 Mock 測試框架 — Mockito 的使用。

什麽是 Mock 測試

Mock 測試就是在測試過程中,對於某些不容易構造(如 HttpServletRequest 必須在Servlet 容器中才能構造出來)或者不容易獲取比較復雜的對象(如 JDBC 中的ResultSet 對象),用一個虛擬的對象(Mock 對象)來創建以便測試的測試方法。

Mock 最大的功能是幫你把單元測試的耦合分解開,如果你的代碼對另一個類或者接口有依賴,它能夠幫你模擬這些依賴,並幫你驗證所調用的依賴的行為。

比如一段代碼有這樣的依賴:

技術分享

當我們需要測試A類的時候,如果沒有 Mock,則我們需要把整個依賴樹都構建出來,而使用 Mock 的話就可以將結構分解開,像下面這樣:

技術分享

Mock 對象使用範疇

真實對象具有不可確定的行為,產生不可預測的效果,(如:股票行情,天氣預報) 真實對象很難被創建的 真實對象的某些行為很難被觸發 真實對象實際上還不存在的(和其他開發小組或者和新的硬件打交道)等等

使用 Mock 對象測試的關鍵步驟

使用一個接口來描述這個對象 在產品代碼中實現這個接口 在測試代碼中實現這個接口 在被測試代碼中只是通過接口來引用對象,所以它不知道這個引用的對象是真實對象,還是 Mock 對象。

Java Mock 測試

目前,在 Java 陣營中主要的 Mock 測試工具有 Mockito,JMock,EasyMock 等。

關於這些框架的比較,不是本文的重點。本文著重介紹 Mockito 的使用。

Mockito 的特性

Mockito 是美味的 Java 單元測試 Mock 框架,開源。

大多 Java Mock 庫如 EasyMock 或 JMock 都是 expect-run-verify (期望-運行-驗證)方式,而 Mockito 則使用更簡單,更直觀的方法:在執行後的互動中提問。使用 Mockito,你可以驗證任何你想要的。而那些使用 expect-run-verify 方式的庫,你常常被迫查看無關的交互。

非 expect-run-verify 方式 也意味著,Mockito 無需準備昂貴的前期啟動。他們的目標是透明的,讓開發人員專註於測試選定的行為。

Mockito 擁有的非常少的 API,所有開始使用 Mockito,幾乎沒有時間成本。因為只有一種創造 mock 的方式。只要記住,在執行前 stub,而後在交互中驗證。你很快就會發現這樣 TDD java 代碼是多麽自然。

類似 EasyMock 的語法來的,所以你可以放心地重構。Mockito 並不需要“expectation(期望)”的概念。只有 stub 和驗證。

Mockito 實現了 Gerard Meszaros 所謂的 Test Spy.

其他的一些特點:

  • 可以 mock 具體類而不單止是接口
  • 一點註解語法糖 - @Mock
  • 幹凈的驗證錯誤是 – 點擊堆棧跟蹤,看看在測試中的失敗驗證;點擊異常的原因來導航到代碼中的實際互動。堆棧跟蹤總是幹幹凈凈。
  • 允許靈活有序的驗證(例如:你任意有序 verify,而不是每一個單獨的交互)
  • 支持“詳細的用戶號碼的時間”以及“至少一次”驗證
  • 靈活的驗證或使用參數匹配器的 stub (anyObject()anyString()refEq() 用於基於反射的相等匹配)
  • 允許創建自定義的參數匹配器或者使用現有的 hamcrest 匹配器

Mockito 入門

聲明 mockito 依賴

Gradle 用戶可以使用:

1 2 repositories { jcenter() } dependencies { testCompile "org.mockito:mockito-core:1.+" }

Maven 用戶可以使用:http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.mockito%22%2C%20a%3A%22mockito-core%22

Mockito 自動發布到 http://jcenter.bintray.com/org/mockito/mockito-core/ 並同步到 Maven Central Repository

示例

1.驗證行為

1 2 3 4 5 6 7 8 9 10 11 12 13 //Let‘s import Mockito statically so that the code looks clearer import static org.mockito.Mockito.*; //mock creation List mockedList = mock(List.class); //using mock object mockedList.add("one"); mockedList.clear(); //verification verify(mockedList).add("one"); verify(mockedList).clear();

一旦創建 mock 將會記得所有的交互。你可以選擇驗證你感興趣的任何交互

2.stubbing

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //You can mock concrete classes, not just interfaces LinkedList mockedList = mock(LinkedList.class); //stubbing when(mockedList.get(0)).thenReturn("first"); when(mockedList.get(1)).thenThrow(new RuntimeException()); //following prints "first" System.out.println(mockedList.get(0)); //following throws runtime exception System.out.println(mockedList.get(1)); //following prints "null" because get(999) was not stubbed System.out.println(mockedList.get(999)); //Although it is possible to verify a stubbed invocation, usually it‘s just redundant //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed). //If your code doesn‘t care what get(0) returns, then it should not be stubbed. Not convinced? See here. verify(mockedList).get(0);
  • 默認情況下,所有方法都會返回值,一個 mock 將返回要麽 null,一個原始/基本類型的包裝值或適當的空集。例如,對於一個 int/Integer 就是 0,而對於 boolean/Boolean 就是 false。
  • Stubbing 可以被覆蓋。
  • 一旦 stub,該方法將始終返回一個 stub 的值,無論它有多少次被調用。
  • 最後的 stubbing 是很重要的 – 當你使用相同的參數 stub 多次同樣的方法。換句話說:stubbing 的順序是重要的,但它唯一有意義的卻很少,例如當 stubbing 完全相同的方法調用,或者有時當參數匹配器的使用,等等。

3.參數匹配器

Mockito 驗證參數值使用 Java 方式:通過使用 equals() 方法。有時,當需要額外的靈活性,可以使用參數匹配器:

1 2 3 4 5 6 7 8 9 10 11 //stubbing using built-in anyInt() argument matcher when(mockedList.get(anyInt())).thenReturn("element"); //stubbing using custom matcher (let‘s say isValid() returns your own matcher implementation): when(mockedList.contains(argThat(isValid()))).thenReturn("element"); //following prints "element" System.out.println(mockedList.get(999)); //you can also verify using an argument matcher verify(mockedList).get(anyInt());

參數匹配器允許靈活的驗證或 stubbing。點擊這裏查看更多內置的匹配器和自定義的參數匹配器/ hamcrest匹配器的例子。

自定義參數的匹配信息,請查看 Javadoc 中 ArgumentMatcher 類。

如果你正在使用參數的匹配,所有的參數都由匹配器來提供。

下面的示例演示驗證,但同樣適用於 stubbing:

1 2 3 4 5 verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //above is correct - eq() is also an argument matcher verify(mock).someMethod(anyInt(), anyString(), "third argument"); //above is incorrect - exception will be thrown because third argument is given without an argument matcher.

4.調用額外的調用數字/at least x / never

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 //using mock mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //following two verifications work exactly the same - times(1) is used by default verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); //exact number of invocations verification verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //verification using never(). never() is an alias to times(0) verify(mockedList, never()).add("never happened"); //verification using atLeast()/atMost() verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("five times"); verify(mockedList, atMost(5)).add("three times");

times(1) 是默認的,因此,使用的 times(1) 可以顯示的省略。

5.Stubbing void 方法處理異常

1 2 3 4 doThrow(new RuntimeException()).when(mockedList).clear(); //following throws RuntimeException: mockedList.clear();

6.有序的驗證

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // A. Single mock whose methods must be invoked in a particular order List singleMock = mock(List.class); //using a single mock singleMock.add("was added first"); singleMock.add("was added second"); //create an inOrder verifier for a single mock InOrder inOrder = inOrder(singleMock); //following will make sure that add is first called with "was added first, then with "was added second" inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second"); // B. Multiple mocks that must be used in a particular order List firstMock = mock(List.class); List secondMock = mock(List.class); //using mocks firstMock.add("was called first"); secondMock.add("was called second"); //create inOrder object passing any mocks that need to be verified in order InOrder inOrder = inOrder(firstMock, secondMock); //following will make sure that firstMock was called before secondMock inOrder.verify(firstMock).add("was called first"); inOrder.verify(secondMock).add("was called second"); // Oh, and A + B can be mixed together at will

有序驗證是為了靈活 – 你不必一個接一個驗證所有的交互。

此外,您還可以通過創建 InOrder 對象傳遞只與有序驗證相關的 mock 。

7. 確保 mock 上不會發生交互

1 2 3 4 5 6 7 8 9 10 11 //using mocks - only mockOne is interacted mockOne.add("one"); //ordinary verification verify(mockOne).add("one"); //verify that method was never called on a mock verify(mockOne, never()).add("two"); //verify that other mocks were not interacted verifyZeroInteractions(mockTwo, mockThree);

8.尋找多余的調用

1 2 3 4 5 6 7 8 //using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); //following verification will fail verifyNoMoreInteractions(mockedList);

註意:不建議 verifyNoMoreInteractions() 在每個測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個方便的斷言。只有與它的相關時才使用它。濫用它導致難以維護。

9. 標準創建 mock 方式 – 使用 @Mock 註解

  • 最小化可重用 mock 創建代碼
  • 使測試類更加可讀性
  • 使驗證錯誤更加易讀,因為字段名稱用於唯一識別 mockpublic class ArticleManagerTest {
    1 2 3 4 5 @Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Mock private UserProvider userProvider; private ArticleManager manager;

在基礎類或者測試 runner 裏面,使用如下:

1 MockitoAnnotations.initMocks(testClass);

可以使用內建 runner: MockitoJUnitRunner 或者 rule: MockitoRule

更多詳見 MockitoAnnotations

10. Stubbing 連續調用(叠代器式的 stubbing)

1 2 3 4 5 6 7 8 9 10 11 12 when(mock.someMethod("some arg")) .thenThrow(new RuntimeException()) .thenReturn("foo"); //First call: throws runtime exception: mock.someMethod("some arg"); //Second call: prints "foo" System.out.println(mock.someMethod("some arg")); //Any consecutive call: prints "foo" as well (last stubbing wins). System.out.println(mock.someMethod("some arg"));

下面是一個精簡版本:

1 2 when(mock.someMethod("some arg")) .thenReturn("one", "two", "three");

11. 回調 Stubbing

允許使用泛型 Answer 接口。

然而,這是不包括在最初的 Mockito 另一個有爭議的功能。我們建議您只需用thenReturn() 或 thenThrow() 來 stubbing ,這在測試/測試驅動中應用簡潔與簡單的代碼足夠了。但是,如果你有一個需要 stub 到泛型 Answer 接口,這裏是一個例子:

1 2 3 4 5 6 7 8 9 10 when(mock.someMethod(anyString())).thenAnswer(new Answer() { Object answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); return "called with arguments: " + args; } }); //the following prints "called with arguments: foo" System.out.println(mock.someMethod("foo"));

12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 家族方法

Stubbing void 方法,需要不同的 when(Object) ,因為編譯器不喜歡括號內無效的方法…

在 用於 Stubbing void 方法中,doThrow(Throwable…) 取代 stubVoid(Object)。主要原因是提高可讀性和與 doAnswer() 保持一致性。

當你想用 stub void 方法 使用 doThrow():

1 2 3 4 doThrow(new RuntimeException()).when(mockedList).clear(); //following throws RuntimeException: mockedList.clear();

在調用 when() 的相應地方可以使用 oThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),當:

  • stub void 方法
  • stub 方法在 spy 對象(見下面)
  • 可以不止一次的 stub 相同的方法,在測試的中期來改變 mock 的行為

但你更加傾向於使用這些方法來代替 when(),在所有的 stubbing 調用。可以閱讀更多關於這些方法的描述:

doReturn(Object))

doThrow(Throwable…))

doThrow(Class))

doAnswer(Answer))

doNothing())

doCallRealMethod())

參考

  • http://martinfowler.com/articles/mocksArentStubs.html
  • http://mockito.org/

Mockito 簡明教程