天天看點

使用Junit和mockito寫單測的一些注意點

使用Mockito和junit進行單測的一些要點: 1,總的來說寫一個單測需要提前進行三件事 a,看好你要測的函數的入參,建構出一個入參; b,詳細看好你的函數中依賴了哪些寫好的函數,這些函數需要進行 @Mock聲明,将他們mock掉,使得你的單測隻測試你寫的邏輯代碼;

c,判斷你要得到什麼樣的結果,也就是你的函數要改變哪些變量的值,然後在UT的最後用Assert斷言來對這些期望值進行預測判斷

下面寫一個例子,本例要驗證fillModel這個函數,它的作用是将一個List<Models>中每一個model的defined屬性設定為true,需要調用外部的依賴itemService來擷取一個model2的defined值,然後将這個值填入model的defined屬性中。

本例中輸入參數是一個List<Models>,一個model裡填入kdtId和id兩個屬性,是以首先進行參數準備(見代碼);

本例需要依賴itemService下的getSpuMap方法,希望這個方法傳回一個map,而這個map中value元素model2的defined屬性經過這個方法被設定為true,注意這個方法不是我們寫的,是以在這裡需要被mock掉,而mock掉後傳回的結果是希望含有defined屬性為true的。

是以,首先我們構造一個這個itemService方法的傳回值,也就是一個map,這個map的value是一個model2類,而model2的defined被我們預先設定為true;

然後用when語句mock掉itemService方法,使其傳回我們構造好的這個map:

when(itemService.getSpuMap(anyLong(), anyList())).thenReturn(map);
           

最後調用我們要測驗的方法,然後檢視調用後model的defined是否和我們預設的model2的值一樣。

需要注意的是,因為我們要測試的fillModel這個方法是需要被實際執行的,不能被mock,是以這個方法的類(通常也就是你的測試類對應的方法類)需要加上@InjectMock注解。而其中依賴的itemService.getSpuMap方法不是我們寫的,我們隻是依賴于它的傳回值,這個類的初始化要加上@Mock注解。

@Test
    public void test_fillModel(){

        //參數準備
        Long kdtId = 100L;
        Long id = 10002L;
        Model model = new Model();
        model.setKdtId(kdtId);
        model.setId(id);
        List<Model> models = Lists.newArrayList(model);

        //做好預期的結果
        Map<Long, Model2> map = new HashMap<>();
        Model2 model2 = new Model2();
        model2.setItemId(id);
        model2.setKdtId(kdtId);
        model2.setDefined(true);
        spuMap.put(10002L,model2);

        //通過when語句mock出fillHasMultiSku函數中所依賴的getSpuMap資源,該資源輸入任意參數,得到之前做好的預期結果spyMap
        when(itemService.getSpuMap(anyLong(), anyList())).thenReturn(map);
        //實際執行fillHasMultiSku函數,models中填入資訊
        itemListInnerService.fillModel(models);

        Assert.assertEquals(models.get(0).isDefined(),itemSkuTotalModel.isDefined());
    }
           

另外還有很多實用Mockito進行測試的小問題,舉幾個例子:

1,巧用verify語句

verify是用來驗證某函數的執行與否,執行幾次,沒有被執行等

@Test
	public void verifying_number_of_invocations(){
		List list = mock(List.class);
		list.add(1);
		list.add(2);
		list.add(2);
		list.add(3);
		list.add(3);
		list.add(3);
		//驗證是否被調用一次,等效于下面的times(1)
		verify(list).add(1);
		verify(list,times(1)).add(1);
		//驗證是否被調用2次
		verify(list,times(2)).add(2);
		//驗證是否被調用3次
		verify(list,times(3)).add(3);
		//驗證是否從未被調用過
		verify(list,never()).add(4);
		//驗證至少調用一次
		verify(list,atLeastOnce()).add(1);
		//驗證至少調用2次
		verify(list,atLeast(2)).add(2);
		//驗證至多調用3次
		verify(list,atMost(3)).add(3);
           

2,用doThrow驗證抛出異常

@Test(expected = RuntimeException.class)  
public void doThrow_when(){  
    List list = mock(List.class);  
    doThrow(new RuntimeException()).when(list).add(1);  
    list.add(1);  
} 
           

3,用spy來真正調用真實的api

@Test  
public void real_partial_mock(){  
    //通過spy來調用真實的api  
    List list = spy(new ArrayList());  
    assertEquals(0,list.size());  
    A a  = mock(A.class);  
    //通過thenCallRealMethod來調用真實的api  
    when(a.doSomething(anyInt())).thenCallRealMethod();  
    assertEquals(999,a.doSomething(999));  
}  
  
  
class A{  
    public int doSomething(int i){  
        return i;  
    }  
} 
           

4,使用 new Answer()來對未預設的調用更改預設期望值

@Test  
public void unstubbed_invocations(){  
    //mock對象使用Answer來對未預設的調用傳回預設期望值  
    List mock = mock(List.class,new Answer() {  
        @Override  
        public Object answer(InvocationOnMock invocation) throws Throwable {  
            return 999;  
        }  
    });  
    //下面的get(1)沒有預設,通常情況下會傳回NULL,但是使用了Answer改變了預設期望值  
    assertEquals(999, mock.get(1));  
    //下面的size()沒有預設,通常情況下會傳回0,但是使用了Answer改變了預設期望值  
    assertEquals(999,mock.size());  
}  
           

Mock的使用相對簡單,但是有很多小細節需要注意,以後使用過程中遇到的問題會更在後面。

繼續閱讀