Mockito 简明教程
原文同步至 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 用戶可以使用:
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.驗證行為
//Let's import Mockito statically so that the code looks clearerimport static org.mockito.Mockito.*;//mock creationList mockedList = mock(List.class);//using mock objectmockedList.add("one");mockedList.clear();//verificationverify(mockedList).add("one");verify(mockedList).clear();一旦創建 mock 將會記得所有的交互。你可以選擇驗證你感興趣的任何交互
2.stubbing
//You can mock concrete classes, not just interfacesLinkedList mockedList = mock(LinkedList.class);//stubbingwhen(mockedList.get(0)).thenReturn("first");when(mockedList.get(1)).thenThrow(new RuntimeException());//following prints "first"System.out.println(mockedList.get(0));//following throws runtime exceptionSystem.out.println(mockedList.get(1));//following prints "null" because get(999) was not stubbedSystem.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() 方法。有時,當需要額外的靈活性,可以使用參數匹配器:
//stubbing using built-in anyInt() argument matcherwhen(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 matcherverify(mockedList).get(anyInt());參數匹配器允許靈活的驗證或 stubbing。點擊這里查看更多內置的匹配器和自定義的參數匹配器/ hamcrest匹配器的例子。
自定義參數的匹配信息,請查看 Javadoc 中 ArgumentMatcher 類。
如果你正在使用參數的匹配,所有的參數都由匹配器來提供。
下面的示例演示驗證,但同樣適用于 stubbing:
verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); //above is correct - eq() is also an argument matcherverify(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
//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 方法處理異常
doThrow(new RuntimeException()).when(mockedList).clear();//following throws RuntimeException: mockedList.clear();6.有序的驗證
// 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 上不會發生交互
//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.尋找多余的調用
//using mocks mockedList.add("one"); mockedList.add("two");verify(mockedList).add("one");//following verification will fail verifyNoMoreInteractions(mockedList);注意:不建議 verifyNoMoreInteractions() 在每個測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個方便的斷言。只有與它的相關時才使用它。濫用它導致難以維護。
9. 標準創建 mock 方式 - 使用 @Mock 注解
-
最小化可重用 mock 創建代碼
-
使測試類更加可讀性
-
使驗證錯誤更加易讀,因為字段名稱用于唯一識別 mock
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;@Mock private ArticleDatabase database;@Mock private UserProvider userProvider;private ArticleManager manager;
在基礎類或者測試 runner 里面,使用如下:
MockitoAnnotations.initMocks(testClass);可以使用內建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
更多詳見 MockitoAnnotations
10. Stubbing 連續調用(迭代器式的 stubbing)
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"));下面是一個精簡版本:
when(mock.someMethod("some arg")).thenReturn("one", "two", "three");11. 回調 Stubbing
允許使用泛型 Answer 接口。
然而,這是不包括在最初的 Mockito 另一個有爭議的功能。我們建議您只需用thenReturn() 或 thenThrow() 來 stubbing ,這在測試/測試驅動中應用簡潔與簡單的代碼足夠了。但是,如果你有一個需要 stub 到泛型 Answer 接口,這里是一個例子:
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():
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/
轉載于:https://my.oschina.net/waylau/blog/617403
總結
以上是生活随笔為你收集整理的Mockito 简明教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache 2,4版本 编译与安装 R
- 下一篇: atom 不能写入