Mockito測試


Mockito
一 mockito基本概念
Mock測試是單元測試的重要方法之一,而Mockito作為一個流行的Mock框架,簡單易學,且有非常簡潔的API,測試代碼的可讀性很高。
Mock測試就是在測試過程中,對於一些不容易構造(如HttpServletRequest必須在Servlet容器中才能構造出來)或者說獲取比較復雜的對象(如JDBC中的ResultSet對象)或者說我們並不需要關注的對象,用一個虛擬的對象(Mock對象)來創建方便測試的測試方法。
Mock最大的功能是可以幫我們把單元測試的耦合分解開,如果代碼中對另一個類或接口有依賴,它就能幫你模擬這些依賴,並幫你驗證所調用的依賴的行為。
Java中目前主要的Mock測試工具有Mockito,JMock,EasyMock等等,很多Java Mock庫如EasyMock或JMock都是expect-run-verify(期望-運行-測試)的方式,而Mockito則更簡單:在執行后的互動中提問。使用Mockito主要記住,在執行前stub,而后在交互中驗證即可。
 
那么什么是stub呢?了解一下Stub和Mock的區別。
Stub對象用來提供測試時所需要的測試數據,可以對各種交互設置相應的回應,比如說設置方法調用的返回值等等。我們在Mockito中可以通過when(...)thenReturn(...)來設置方法調用的返回值。
Mock對象用來驗證測試中所依賴的對象間的交互能否達到預期,我們可以在Mockito中用verify(...)methodXxx(...)語法來驗證methodXxx方法是否按預期被調用。它也有限制,對於final類、匿名類和Java的基本類型是無法mock的。
 
二 具體使用
1.如果項目是Maven管理的,那么就要在pom.xml中加入依賴:
<dependency> 
    <groupId>org.mockito</groupId> 
    <artifactId>mockito-all</artifactId> 
    <version>1.8.5</version> 
    <scope>test</scope> 
</dependency>

然后在程序中直接import static org.mockito.Mockito.*; 即可

2.首先看一個例子:
List mockList = mock( List.class ); 
when( mockList .get(0) ).thenReturn( 1 ); 
assertEquals( "預期返回1", 1, mockList .get( 0 ) );

mockList模擬了List的對象,擁有List的所有方法和屬性。when(...).thenReturn(...)是指當執行這個方法的時候,返回指定的值。相當於模擬配置對象的過程,為某些條件給定一個預期的返回值。

這里的List是Java.util.List接口,並不是實現類,你也可以使用實現類,我們可以使用它們作為打樁對象。這里的打樁(Stub)也可以叫存根,就是把所需的測試數據塞進對象中,關注的是輸入和輸出。這里的when(...).thenReturn(...)就是在定義對象方法和參數(輸入),然后在thenReturn()中指定結果(輸出),這個過程就是Stub打樁,一旦這個方法被Stub了,就會一直返回這個stub的值。當我們連續兩次為同一個方法使用stub的時候,最后的那個stub是有效的。

一旦mock了一個對象之后,mock對象會覆蓋掉整個被mock的對象,因此如果沒有stub方法,就只能返回默認值。當我們mock一個接口時,很多成員方法只是一個簽名,並沒有實現,需要我們手動寫出這些實現方法。比如說,我們模擬request請求對象,被測試的代碼中使用了HttpServletRequest的什么方法,就要寫出相應的實現方法:

HttpServletRequest request = mock(HttpServletRequest.class); 
when(request.getParameter("foo")).thenReturn("boo");

如果我們不通過when().thenReturn()返回預期值,mockito就會默認返回null,也不會報錯說這個方法找不到。mock實例默認的會給所有的方法添加基本實現:返回null或者空集合,或者0等基本類型的值。

3.迭代風格

打樁支持迭代風格的返回值設定,如:
// 第一種方式   
when(i.next()).thenReturn("Hello").thenReturn("World"); 
// 第二種方式 
when(i.next()).thenReturn("Hello", "World"); 
// 第三種方式,都是等價的 
when(i.next()).thenReturn("Hello"); 
when(i.next()).thenReturn("World");

4.測試無返回值的方法

如果被測試的方法沒有返回值,那么測試方法是:
doNothing().when(i).remove();   
doNothing().when(obj).notify(); 
// 或直接 
when(obj).notify();

5.拋出異常

mockito還能對被測試的方法強行拋出異常:
when(i.next()).thenThrow(new RuntimeException());
doThrow(new RuntimeException()).when(i).remove();
支持迭代風格:
doNothing().doThrow(new RuntimeException()).when(i).remove();  //第一次調用remove方法什么都不做,第二次調用拋出RuntimeException異常。  

6.參數匹配器

Argument Matcher(參數匹配器)
Mockito通過equals()方法,來對方法參數進行驗證。但是有時候我們需要更加靈活的參數需求,比如,匹配任何的String類型的參數等等。參數匹配器就是一個能夠滿足這些需求的工具。
Mockito框架中的Matchers類內建了很多參數匹配器,我們常用的Mockito對象就是繼承自Matchers。比如anyInt()匹配任何int類型的參數,anyString()匹配任何字符串...
@Test 
public void argumentMatchersTest(){ 
    List<String> mock = mock(List.class); 
    when(mock.get(anyInt())).thenReturn("Hello").thenReturn("World"); 
    String result=mock.get(100)+" "+mock.get(200); 
    verify(mock,times(2)).get(anyInt()); 
    assertEquals("Hello World",result); 
} 
首先mock了List接口,然后用迭代的方式模擬了get方法的返回值,這里用了anyInt()參數匹配器來匹配任何的int類型的參數。所以當第一次調用get方法時輸入任意參數為100方法返回”Hello”,第二次調用時輸入任意參數200返回值”World”。
這里需要注意:
如果使用了參數匹配器,那么所有的參數需要由匹配器來提供,否則將會報錯。假如我們使用參數匹配器stubbing了mock對象的方法,那么在verify的時候也需要使用它。如:
@Test 
public void argumentMatchersTest(){ 
    Map mapMock = mock(Map.class); 
    when(mapMock.put(anyInt(), anyString())).thenReturn("world"); 
    mapMock.put(1, "hello"); 
    verify(mapMock).put(anyInt(), eq("hello")); 
}   

在最后的驗證時如果只輸入字符串”hello”是會報錯的,必須使用Matchers類內建的eq方法。如果將anyInt()換成1進行驗證也需要用eq(1)。

7.驗證Verify

之前的when(...).thenReturn(...)屬於狀態測試,有些時候,測試並不關心返回結果,而是關心方法是否被正確的參數調用過,這時候就應該使用驗證方法了。從概念上講,就是和狀態測試不同的“行為測試”了。一旦通過mock對模擬對象打樁,意味着mockito會記錄着這個模擬對象調用了什么方法,調用了多少次,最后由用戶決定是否需要進行驗證,即verify()方法。

mockedList.add("one"); 
mockedList.add("two"); 
verify(mockedList).add("one");

verify 內部跟蹤了所有的方法調用和參數的調用情況,然后會返回一個結果,說明是否通過。

Map mock = Mockito.mock( Map.class ); 
when( mock.get( "city" ) ).thenReturn( "廣州" ); 
// 關注參數有否傳入 
verify(mock).get( Matchers.eq( "city" ) ); 
// 關注調用的次數 
verify(mock, times( 2 )); 
也就是說,這是對歷史記錄作一種回溯校驗的處理。
Mockito 除了提供 times(N) 方法供我們調用外,還提供了很多可選的方法:
never() 沒有被調用,相當於 times(0)
atLeast(N) 至少被調用 N 次
atLeastOnce() 相當於 atLeast(1)
atMost(N) 最多被調用 N 次
verify 也可以像 when 那樣使用模擬參數,若方法中的某一個參數使用了matcher,則所有的參數都必須使用 matcher。
// correct   
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));   
// will throw exception   
verify(mock).someMethod(anyInt(), anyString(), "third argument"); 

8.Spy

Mock對象是能調用stubbed方法,調用不了它真實的方法,但是Mockito可以監視一個真實的對象,這時對它進行方法調用時它將調用真實的方法,同時也可以stubbing這個對象的方法讓它返回我們的期望值。同時,我們也可以用verify進行驗證。
監視對象
監視一個對象需要調用spy(T object)方法,如:List spy = spy(new LinkedList());那么spy變量就在監視LinkedList實例。
被監視對象的Stubbing
stubbing被監視對象的方法時要慎用when(Object),如:
List spy = spy(new LinkedList()); 
//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty) 
when(spy.get(0)).thenReturn("foo"); 
//You have to use doReturn() for stubbing 
doReturn("foo").when(spy).get(0);

當調用when(spy.get(0)).thenReturn("foo")時,會調用真實對象的get(0),由於list是空的所以會拋出IndexOutOfBoundsException異常,用doReturn可以避免這種情況的發生,因為它不會去調用get(0)方法。

9.使用ArgumentCaptor(參數捕獲器) 捕獲方法參數進行驗證

在某些場景中,不光要對方法的返回值和調用進行驗證,同時需要驗證一系列交互后所傳入方法的參數,這時我們可以用參數捕獲器來捕獲傳入方法的參數進行驗證,看它是否符合我們的要求。

通過ArgumentCaptor對象的forClass(Class<T> clazz)方法來構建ArgumentCaptor對象然后就可以在驗證時對方法的參數進行捕獲,最后驗證捕獲的參數值。如果方法有多個參數都要捕獲驗證,那就需要創建多個ArgumentCaptor對象處理。
argument.capture()  捕獲方法參數
argument.getValue() 獲取方法參數值,如果方法進行了多次調用,它將返回最后一個參數值
argument.getAllValues() 方法進行多次調用后,返回多個參數值
@Test 
public void argumentCaptorTest() { 
    List mock = mock(List.class); 
    List mock2 = mock(List.class); 
    mock.add("John"); 
    mock2.add("Brian"); 
    mock2.add("Jim"); 

    ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); 

    verify(mock).add(argument.capture()); 
    assertEquals("John", argument.getValue()); 

    verify(mock2, times(2)).add(argument.capture()); 

    assertEquals("Jim", argument.getValue()); 
    assertArrayEquals(new Object[]{"Brian","Jim"},argument.getAllValues().toArray()); 
} 
在某種程度上參數捕獲器和參數匹配器有很大的相關性。它們都用來確保傳入mock對象參數的正確性。然而,當自定義的參數匹配器的重用性較差時,用參數捕獲器會更合適,只需在最后對參數進行驗證即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM