Mockito 簡明教程
(點擊
上方公眾號
,可快速關注)
來源:waylau ,
my.oschina.net/waylau/blog/617403
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 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
//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() 方法。有時,當需要額外的靈活性,可以使用參數匹配器:
//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:
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
//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 創建代碼
使測試類更加可讀性
使驗證錯誤更加易讀,因為欄位名稱用於唯一識別 mockpublic 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))
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doReturn(java.lang.Object
doThrow(Throwable…))
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doThrow(java.lang.Throwable...
doThrow(Class))
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doThrow(java.lang.Class
doAnswer(Answer))
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doAnswer(org.mockito.stubbing.Answer
doNothing())
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doNothing(
doCallRealMethod())
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doCallRealMethod(
參考
http://martinfowler.com/articles/mocksArentStubs.html
http://mockito.org/
【關於投稿】
如果大家有原創好文投稿,請直接給公號發送留言。
① 留言格式:
【投稿】+《 文章標題》+ 文章鏈接
② 示例:
【投稿】《不要自稱是程序員,我十多年的 IT 職場總結》:http://blog.jobbole.com/94148/
③ 最後請附上您的個人簡介哈~
看完本文有收穫?請轉發分享給更多人
關注「ImportNew」,提升Java技能
※一份不能錯過的 Docker 實戰指南
※Ubuntu 更改 MySQL 資料庫數據存儲目錄
TAG:ImportNew |