Java測試框架Mockito的簡明教程
什么是 Mock 測試
Mock 測試就是在測試過程中,對于某些不容易構(gòu)造(如 HttpServletRequest 必須在Servlet 容器中才能構(gòu)造出來)或者不容易獲取比較復(fù)雜的對象(如 JDBC 中的ResultSet 對象),用一個虛擬的對象(Mock 對象)來創(chuàng)建以便測試的測試方法。
Mock 最大的功能是幫你把單元測試的耦合分解開,如果你的代碼對另一個類或者接口有依賴,它能夠幫你模擬這些依賴,并幫你驗證所調(diào)用的依賴的行為。
比如一段代碼有這樣的依賴:
當(dāng)我們需要測試A類的時候,如果沒有 Mock,則我們需要把整個依賴樹都構(gòu)建出來,而使用 Mock 的話就可以將結(jié)構(gòu)分解開,像下面這樣:
Mock 對象使用范疇
真實對象具有不可確定的行為,產(chǎn)生不可預(yù)測的效果(如:股票行情,天氣預(yù)報) :
- 真實對象很難被創(chuàng)建的
- 真實對象的某些行為很難被觸發(fā)
- 真實對象實際上還不存在的(和其他開發(fā)小組或者和新的硬件打交道)等等
使用 Mock 對象測試的關(guān)鍵步驟
- 使用一個接口來描述這個對象
- 在產(chǎn)品代碼中實現(xiàn)這個接口
- 在測試代碼中實現(xiàn)這個接口
- 在被測試代碼中只是通過接口來引用對象,所以它不知道這個引用的對象是真實對象,還是 Mock 對象。
Mock 與Stub 的區(qū)別
Mock 不是 Stub,兩者是有區(qū)別的:
- 前者被稱為 mockist TDD,而后者一般稱為 classic TDD ;
- 前者是基于行為的驗證(behavior verification),后者是基于狀態(tài)的驗證 (state verification);
- 前者使用的是模擬的對象,而后者使用的是真實的對象。
Java Mock 測試
目前,在 Java 陣營中主要的 Mock 測試工具有 Mockito,JMock,EasyMock 等。
關(guān)于這些框架的比較,不是本文的重點。本文著重介紹 Mockito 的使用。
Mockito 的特性
Mockito 是美味的 Java 單元測試 Mock 框架,開源。
大多 Java Mock 庫如 EasyMock 或 JMock 都是 expect-run-verify (期望-運行-驗證)方式,而 Mockito 則使用更簡單,更直觀的方法:在執(zhí)行后的互動中提問。使用 Mockito,你可以驗證任何你想要的。而那些使用 expect-run-verify 方式的庫,你常常被迫查看無關(guān)的交互。
非 expect-run-verify 方式 也意味著,Mockito 無需準(zhǔn)備昂貴的前期啟動。他們的目標(biāo)是透明的,讓開發(fā)人員專注于測試選定的行為。
Mockito 擁有的非常少的 API,所有開始使用 Mockito,幾乎沒有時間成本。因為只有一種創(chuàng)造 mock 的方式。只要記住,在執(zhí)行前 stub,而后在交互中驗證。你很快就會發(fā)現(xiàn)這樣 TDD java 代碼是多么自然。
類似 EasyMock 的語法來的,所以你可以放心地重構(gòu)。Mockito 并不需要“expectation(期望)”的概念。只有 stub 和驗證。
Mockito 實現(xiàn)了 Gerard Meszaros 所謂的 Test Spy.
其他的一些特點:
- 可以 mock 具體類而不單止是接口
- 一點注解語法糖 - @Mock
- 干凈的驗證錯誤是 - 點擊堆棧跟蹤,看看在測試中的失敗驗證;點擊異常的原因來導(dǎo)航到代碼中的實際互動。堆棧跟蹤總是干干凈凈。
- 允許靈活有序的驗證(例如:你任意有序 verify,而不是每一個單獨的交互)
- 支持“詳細(xì)的用戶號碼的時間”以及“至少一次”驗證
- 靈活的驗證或使用參數(shù)匹配器的 stub (anyObject(),anyString() 或 refEq() 用于基于反射的相等匹配)
- 允許創(chuàng)建自定義的參數(shù)匹配器或者使用現(xiàn)有的 hamcrest 匹配器
Mockito 入門
聲明 mockito 依賴
Gradle 用戶可以使用:
repositories { jcenter() } dependencies { testCompile "org.mockito:mockito-core:1.+" }
示例
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();
一旦創(chuàng)建 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);
- 默認(rèn)情況下,所有方法都會返回值,一個 mock 將返回要么 null,一個原始/基本類型的包裝值或適當(dāng)?shù)目占@?,對于一個 int/Integer 就是 0,而對于 boolean/Boolean 就是 false。
- Stubbing 可以被覆蓋。
- 一旦 stub,該方法將始終返回一個 stub 的值,無論它有多少次被調(diào)用。
- 最后的 stubbing 是很重要的 - 當(dāng)你使用相同的參數(shù) stub 多次同樣的方法。換句話說:stubbing 的順序是重要的,但它唯一有意義的卻很少,例如當(dāng) stubbing 完全相同的方法調(diào)用,或者有時當(dāng)參數(shù)匹配器的使用,等等。
3.參數(shù)匹配器
Mockito 驗證參數(shù)值使用 Java 方式:通過使用 equals() 方法。有時,當(dāng)需要額外的靈活性,可以使用參數(shù)匹配器:
//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());
參數(shù)匹配器允許靈活的驗證或 stubbing。點擊這里查看更多內(nèi)置的匹配器和自定義的參數(shù)匹配器/ hamcrest匹配器的例子。
自定義參數(shù)的匹配信息,請查看 Javadoc 中 ArgumentMatcher 類。
如果你正在使用參數(shù)的匹配,所有的參數(shù)都由匹配器來提供。
下面的示例演示驗證,但同樣適用于 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.調(diào)用額外的調(diào)用數(shù)字/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) 是默認(rèn)的,因此,使用的 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
有序驗證是為了靈活 - 你不必一個接一個驗證所有的交互。
此外,您還可以通過創(chuàng)建 InOrder 對象傳遞只與有序驗證相關(guān)的 mock 。
7. 確保 mock 上不會發(fā)生交互
//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.尋找多余的調(diào)用
//using mocks mockedList.add("one"); mockedList.add("two"); verify(mockedList).add("one"); //following verification will fail verifyNoMoreInteractions(mockedList);
注意:不建議 verifyNoMoreInteractions() 在每個測試方法中使用。 verifyNoMoreInteractions() 是從交互測試工具包一個方便的斷言。只有與它的相關(guān)時才使用它。濫用它導(dǎo)致難以維護(hù)。
9. 標(biāo)準(zhǔn)創(chuàng)建 mock 方式 - 使用 @Mock 注解
- 最小化可重用 mock 創(chuàng)建代碼
- 使測試類更加可讀性
- 使驗證錯誤更加易讀,因為字段名稱用于唯一識別 mock
public class ArticleManagerTest { @Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Mock private UserProvider userProvider; private ArticleManager manager;
在基礎(chǔ)類或者測試 runner 里面,使用如下:
MockitoAnnotations.initMocks(testClass);
可以使用內(nèi)建 runner: MockitoJUnitRunner 或者 rule: MockitoRule
10. Stubbing 連續(xù)調(diào)用(迭代器式的 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. 回調(diào) Stubbing
允許使用泛型 Answer 接口。
然而,這是不包括在最初的 Mockito 另一個有爭議的功能。我們建議您只需用thenReturn() 或 thenThrow() 來 stubbing ,這在測試/測試驅(qū)動中應(yīng)用簡潔與簡單的代碼足夠了。但是,如果你有一個需要 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) ,因為編譯器不喜歡括號內(nèi)無效的方法…
在 用于 Stubbing void 方法中,doThrow(Throwable…) 取代 stubVoid(Object)。主要原因是提高可讀性和與 doAnswer() 保持一致性。
當(dāng)你想用 stub void 方法 使用 doThrow():
doThrow(new RuntimeException()).when(mockedList).clear(); //following throws RuntimeException: mockedList.clear();
在調(diào)用 when() 的相應(yīng)地方可以使用 oThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),當(dāng):
- stub void 方法
- stub 方法在 spy 對象(見下面)
- 可以不止一次的 stub 相同的方法,在測試的中期來改變 mock 的行為
但你更加傾向于使用這些方法來代替 when(),在所有的 stubbing 調(diào)用。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用jib插件為Java應(yīng)用構(gòu)建鏡像的方法
這篇文章主要介紹了使用jib插件為Java應(yīng)用構(gòu)建鏡像,要是用戶本地沒安裝docker,可以使用jib制作出帶有鏡像的tar文件,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08Java 數(shù)據(jù)結(jié)構(gòu)之刪除鏈表中重復(fù)的結(jié)點
在一個排序的鏈表中,會存在重復(fù)的結(jié)點,如何實現(xiàn)刪除該鏈表中重復(fù)的結(jié)點,重復(fù)的結(jié)點不保留,并返回鏈表頭指針呢?接下來小編將帶你詳細(xì)介紹2021-12-12mybatis如何實現(xiàn)的數(shù)據(jù)庫排序
這篇文章主要介紹了mybatis如何實現(xiàn)的數(shù)據(jù)庫排序,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Java實現(xiàn)順序表和鏈表結(jié)構(gòu)
大家好,本篇文章主要講的是Java實現(xiàn)順序表和鏈表結(jié)構(gòu),感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下2022-02-02Springboot如何優(yōu)雅的關(guān)閉應(yīng)用
這篇文章主要介紹了Springboot如何優(yōu)雅的關(guān)閉應(yīng)用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08@SpringBootConfiguration重復(fù)加載報錯問題解決
@SpringBootApplication?注解的?exclude?屬性用于排除特定的自動配置類,而不是用于排除主配置類本身,因此,不能通過?exclude?屬性來排除主配置類的加載,這篇文章主要介紹了@SpringBootConfiguration重復(fù)加載報錯,需要的朋友可以參考下2024-08-08