使用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的使用相對簡單,但是有很多小細節需要注意,以後使用過程中遇到的問題會更在後面。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章