Java單元測試神器之Mockito
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能提前發現的話,總比執行的時候被別人半夜叫起來修復舒服是吧?