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