什么是 Mock 測試
Mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。什么是不容易構造的對象呢?例如HttpServletRequest,需要在有servlet容器環境中創建獲取。那不容易獲取的對象呢?如一個JedisCluster,需要准備redis相關環境,然后設置進去等等。
Mock 可以分解在單元測試中耦合的其他類或者接口,它能夠幫你模擬這些依賴,並幫你驗證所調用的依賴的行為。
場景事例
當我們需要測試OrderService時,按照我們常規的做法呢,都是要先准備好redis,跟db的環境,然后構造UserService跟CouponService注入進來,此時需要構建完整的依賴樹,其過程是比較繁瑣的,萬一數據庫連不上,依賴找不到,服務掛了... 時間一長可能會打擊我們對項目進行單測的積極性,所以這時候很有必要尋求一種優雅的方式來解決。
鐺鐺鐺~這時候Mockito出現了(java中Mock框架比較多,但是本篇只介紹這個),它會把那些繁瑣的依賴統統轉化為Mock Object,如下圖,這樣我們就可以專注的進行我們的單測,減少在解決依賴上浪費的時間了。
直接開干
關於Mockito的簡介這里就不在贅述了,大家有興趣可以自行去官方文檔查閱,這里主要帶大家了解一些常用的Mock方法。
maven依賴
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.23.4</version> <scope>test</scope> </dependency>
為了代碼測試的方便,直接在測試類中靜態導入 import static org.mockito.Mockito.*;
基礎方法
@Test public void testMockBase(){ //創建ArrayList的Mock對象 List mockList = mock(ArrayList.class); //pass Assert.assertTrue(mockList instanceof ArrayList); //當我們mockList調用方法去add("張三")的時候會返回true when(mockList.add("張三")).thenReturn(true); //當我們mockList調用方法size()的時候返回10 when(mockList.size()).thenReturn(10); //pass Assert.assertTrue(mockList.add("張三")); //pass Assert.assertFalse(mockList.add("李四")); //pass Assert.assertEquals(mockList.size(),10); //null System.out.println(mockList.get(0)); }
mock靜態方法會創建一個Mock對象,由於 Mock對象 並不會真的執行方法中的代碼,所以如果未指定返回值的話會返回默認值(如19行)。第九、十行我們指定了mockList在執行特定方法后需要返回的值,所以在assertTrue校驗是沒問題的,但是add("李四"),我們並沒設置,所以是false。
校驗方法調用次數
//使用mock List mockedList = mock(ArrayList.class); mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //這里默認是判斷該方法調用times(1),同下 verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //從沒調用,times(0) verify(mockedList, never()).add("never happened"); //最少一次,最少幾次,最多幾次 verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times");
其實在上述的代碼中,命名是比較直觀的,所以我這邊就直接注釋在代碼中了。
校驗方法調用時長
//方法執行在100ms以內的時候可以通過 verify(mock, timeout(100)).someMethod(); //同上 verify(mock, timeout(100).times(1)).someMethod(); //方法2次調用均沒超過100ms verify(mock, timeout(100).times(2)).someMethod(); verify(mock, timeout(100).atLeast(2)).someMethod();
通過超時檢測可以校驗我們的方法邏輯會不會有出現問題而導致超時的地方。
參數匹配
linkedList.add("element"); // anyInt() 任何整數我們都返回 element when(linkedList.get(anyInt())).thenReturn("element"); System.out.print(linkedList.get(10));//返回element
方法拋出異常
@Test(expected = RuntimeException.class) public void doThrow(){ List list = mock(List.class); doThrow(new RuntimeException()).when(list).add(1); list.add(1); }
使用注解注入
public class ArticleManagerTest { @Mock private ArticleCalculator calculator; @Mock private ArticleDatabase database; @Mock private UserProvider userProvider; private ArticleManager manager;
要注意的是,通過注解的方式用使用的話,我們必須在添加初始化mock的代碼,不然即使標注了注解也會是null
MockitoAnnotations.initMocks(testClass);
關於Mockito更多詳細的用法,大家可以直接參考官方文檔,因為各種“奇技淫巧”確實比較多,后面也更新對java8 lambda的支持,很多功能還是期待大家去挖掘~
更多詳細用法可直接參考官方文檔:
static.javadoc.io/org.mockito…
相信當你熟練使用Mockito以后,你會愛上寫單測的,也會讓你代碼健壯性有所加強。有些bug能提前發現的話,總比運行的時候被別人半夜叫起來修復舒服是吧?