Why
單元測試幫我們快速驗證代碼流程(超快速得到代碼結果),更早了解程序的問題(發現代碼bug如隱藏的空指針等),讓我們能瞭解需求(測試用例覆蓋所有邏輯鏈路),利於提高代碼能力。
What
常用單元測試框以及常用用法
junit
- 常用註解:
@RunWith:制定測試運行容器
@BeforeClass(測試類啓動時執行一次)
@AfterClass(測試類銷燬時執行一次)
@Test 方法註解,測試用例
@Before 方法註解,測試方法執行前執行,常用作配置或創建對象
@After 方法註解,測試方法執行後執行,常用於資源清理關閉等
@IgnoreTest 忽略測試用例
@Rule 不影響原有case的代碼,減少了特有操作和test case原邏輯的耦合 - Assertions斷言:(或者可使用AssertJ斷言庫有更多斷言方法)
assertEquals:比較值
assertArrayEquals:比較數組或list的值是否相同
assertTrue/AssertFalse:判斷條件真假()
assertNull/assertNotNull:驗證對象是否爲空或不爲空
assertSame/assertNotNull:判斷是否同一引用(==)
fail:強制不通過
assertThat(actual, matcher):按照匹配符規則驗證
matcher匹配符- 一般對象匹配符:
allOf:所有條件都成立才通過,相當於&&
eg:assertThat(testNumb,allOf(greaterThan(8),lessThan(16));
anyOf:所有條件只要有一個成立,相當於||
anything:無論什麼條件,永遠爲true
is:待測的object等於後面給出的object
not:前面待測的object不等於後面給出的object - 字符串匹配符:
containsString:待測字符串包含 子字符串
endsWith/startsWith:待測字符串以子字串結尾/開頭
equalTo:測試數值之間,字符串 之間和對加粗樣式象之間是否相等
equalToIgnoringCase:在忽略大小寫的情況下等於
equalToIgnoringWhiteSpace:忽略頭尾的任意個空格的情況下等於 - 數值匹配符:
closeTo:浮點型數testedDouble在某範圍之內
eg:assertThat(testedDouble, closeTo(20.0,0.5 ));
greaterThan/lessThan:大於/小於
greaterThanOrEqualTo/lessThanOrEqualTo:大於等於/小於等於 - collection相關匹配符:
hasEntry: Map對象含有一個鍵值爲"key"對應元素值爲"value"的Entry項
hasItem:測試的迭代對象iterableObject含有元素“element”項
hasKey:Map對象mapObject含有鍵值“key”
hasValue:Map對象mapObject含有元素值“value”
- 一般對象匹配符:
Mockito
對象mock框架,創建虛擬對象隔離測試依賴,用於給不易構造的對象創建虛擬對象來測試
-
常用註解:
@Mock:定義的mock對象將會被注入到這個待測試的對象中(默認不執行,默認返回null)
@InjectMock:聲明瞭一個待測試的對象()
@Spy:監視真實的對象
@Captor -
常用方法:
when(mockObj).thenReturn(輸出指定值或對象)😕/有返回值的方法
when(moc對象).notify();//模擬無返回值的方法
doThrow(new XXXException()).when(mocObject).callSomeMethod();//模擬拋出異常
verify(mock對象方法,timess(N))//跟蹤所有方法的調用和參數調用情況,驗證行爲是否發生
mock(mockObj,RETURNS_SMART_NULLS);//創建mock對象的可選空參數,避免普通調用時拋出空指針異常
when().thenAnswer(new MyAnswer());//自定義預期返回值
doCallRealMeth().when(mockA).goHome(); //mock對象調用部分真實的方法
ArgumentCaptor驗證傳入方法的參數
ArgumentCaptor.forClass(XXX.class)//構建參數對象
argument.capture();//捕獲參數
argument.getValue();//回去方法參數的值
argument.getAllValues();//獲取多次調用後的參數值 -
斷言
Matchers:類似Junit斷言,比Junit斷言有更多的斷言方法- 數據類型斷言類:
any()/anyInt()/anyString()… - 集合斷言類:
anyList/anyListOf/anyMap/anyMapOf/anySet/anySetOf/anyCollection/anyCollectionOf - 對象斷言:
equal(x)/same(T)/isNull()/notNull()/isNotNull() - 字符串:
contains(String s)/matches(regex)/endsWith/startsWith/ - 參數:
argThat(Matcher)/charThat/intThat…;自定義參數匹配 - 執行順序:
InOrder:驗證執行順序 eg:inOrder(Obj1,Obj2,Obj3);//必須是按照定義好的順序執行否之不通過驗證 - 調用:
verifyZeroInteractions(obj1,obj2);//驗證對象零互動
verifyNoMoreInteractions(obj);//驗證對象是否有未被驗證的互動性爲,有互動行爲通過,有位被驗證的行爲不通過
- 數據類型斷言類:
PowerMock
可以實現完成對private/static/final方法的Mock(模擬)
-
常用註解:
@RunWith(PowerMockRunner.class); //使用PowerMockRunner進行測試非public方法是必須使用此類
@PrepareForTest({MockUtil.class}); //所有需要測試的類列在此處,適用於模擬final類或有final, private, static, native方法的類
@PowerMockIgnore(“javax.management.*”) ;//爲了解決使用powermock後,提示classloader錯誤 -
常用場景:
(1)驗證普通方法public
同Mock
(2)驗證靜態方法static
PowerMockito.mockStatic(XXX.class);
PowerMockito.when(XXX.callStaticMethod([xxx])).thenReturn(x);
Assert…;
(3)驗證私有方法的調用
Whitebox.invokeMethod(xxx,“privateFunc”,arg1,arg2…argn);//獲取私有方法的返回值
(4)驗證方法內new出來的對象(即private對象)
NewObj newObj = PowerMockito.mock(NewObj.class);
PowerMockito.whenNew(NewObj.class).withArguments(T)/.withNoArguments().thenReturn(newObj)
PowerMockito.when(XXX.callSomeMehod([xxx])).thenReturn(X);
Assert…;
(5)驗證私有方法private被調用的方法
@Spy
private XXX xxx;//驗證private方法必須模擬一個真實存在的測試對象
PowerMockito.when(xxx,“privateFunc”).thenReturn(“xxxx”);//模擬私有方法的返回值
PowerMockito.verifyPrivate(xxx, Mockito.times(1));//驗證私有方法被調用
(6)驗證final方法(類似驗證靜態方法)
PowerMockito.mockStatic(XXX.class);
PowerMockito.when(XXX.callStaticMethod([xxx])).thenReturn(x);
Assert…;
(7)驗證單例模式的對象
Whitebox.setInternalState(Singleton.class, “INSTANCE”, singleton);
PowerMockito.when(singleton.max(Mockito.anyInt(), Mockito.anyInt())).then(returnsFirstArg());
Assert…
採坑記錄
實操採坑不間斷更新記錄
(1)RestTemplate:預定義的返回值無法傳遞
坑:直接使用@Mock或者Mockito.mock()方法mock的rest Template對象注入到service層後,再使用Mockito.when(restTemplate.xxx).thenReturn(xxxResponseEntity)驗證預定義的返回值時,返回值無法傳遞到service層,單測走到service層後取返回值會null
填坑:使用SpringBoot的測試註解,將測試環境模擬再SpringBoot容器內,將restTemplate注和serviceImpl都構建再容器內進行測試:
@RunWith(SpringRunner.class)
@SpringTest
@MockBean
private RestTemplate restTemplate;
@Autowired
private XXXServiceImpl xxxServiceImpl;
...
@Test
public void testXX(){
...
String myRespJsonStr = this.mockXXXJson();//構建返回Json字符串
Mockito
.when(restTtemplate.postForEntity(anyString(),any(XXXReeust.class),Mockito.eq(String.class)))
.thenReturn(new Response<>(myRespJsonStr ,HttpStatus.OK));
...
}