以前在開發進行到寫單元測試階段的時候,發現要測試的方法裏面是包含依賴的:外部接口RPC調用、DB調用。在某些狀況下,部分依賴不穩定或者沒法在測試環境調用時,會致使用例偶爾執行失敗。java
另一點,不少用例都是在測試用例的開頭寫了@SpringRunTest
的註解,致使跑用例的時候會啓動整個Spring容器,這樣一來,運行測試用例就很是慢了。當在一些比較大的項目運行用例時,甚至達到了每次啓動容器須要5-6分鐘的時長,漸漸就有點受不了這種操做,每改一行代碼內心都焦急,由於若是錯了的話又要再等5-6分鐘才能看到效果了。後來請教同事和上網搜索,找到了一種比較快且安全的方案,使用Mock框架--Mockito,學習並實踐了一段時間,總結一下使用方法。安全
Mockito是當前最流行的單元測試Mock框架。網絡
Mock的字面意思就是模仿,虛擬,在單元測試中,使用Mock能夠虛擬出一個外部依賴對象。框架
對於在單元測試中一些不容易構造或者不容易獲取的對象(如外部服務),用一個Mock對象來建立,能夠下降測試的複雜度,只關心當前單元測試的方法。ide
單元測試的目的就是爲了驗證一個代碼單元的正確性,真正要驗證的只是某個輸入對應的輸出的正確與否。若是把外部依賴服務引入進來,就會增長原來單元的複雜度,且在該單元中隱形地摻雜了其餘功能的內容。性能
使用Mock對象進行單元測試,開發能夠只關心要測試單元的代碼。單元測試
先看看代碼示例,假設有如下的場景:學習
- 驗證獲取用戶信息接口:包含用戶ID、用戶暱稱、是否vip
- 是否vip須要外部服務VIPService獲取,經過RPC調用,測試環境若是機器性能較差或者網絡很差會致使用例不穩定
- 編寫單元測試判斷用戶VIP信息返回是否正確
需求是判斷獲取用戶信息接口返回的格式是否正確,與vip接口的返回值無關,只要透傳vip接口返回的字段便可,測試代碼以下:測試
@RunWith(PowerMockRunner.class)
public class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private VIPService vipService;
@Test
public void getUserInfo() {
Mockito.when(vipService.isVip(Mockito.anyString())).thenReturn(true);
Result result = userService.getUserInfo("123");
Assert.assertEquals(true, result.getData().get("isVip"));
}
}
複製代碼
解釋下上面代碼用到的幾個註解ui
@Mock:建立一個Mock
@InjectMocks:Mock一個實例,其他用Mock註解建立的mock將被注入到該實例中。
Mockito.when(...).thenReturn(...):Mock方法,若是知足when裏面的條件,返回thenReturn指定的結果。
在這段代碼裏,使用@Mock
註解建立了一個VipService實例,使用@InjectMock
建立了UserService,Mock建立的vipService實例會被注入到UserService的實例中,在寫測試用例的時候就能夠模擬vipService的行爲。
Mockito.when(vipService.isVip(Mockito.anyString())).thenReturn(true);
這段代碼表示無論傳任何參數給vipService.isVip方法,該方法都會返回true,這樣,就不影響獲取用戶信息接口的正常測試,也可使用斷言驗證返回的數據。
以上是使用Mockito實踐最簡單的示例,在生產環境使用過程當中,會有各類各樣的需求須要知足,下面列一下筆者遇到過的場景。
這種場景是,方法裏面聲明瞭可能會拋出A異常,而A異常有多種可能性,不一樣的異常對應不一樣的message,爲了驗證拋出某種A異常後的功能,就須要模擬方法拋出指定message的A異常。
使用方式是定義一個Rule
註解的屬性,在使用時,設置thrown拋出的異常類型和所帶的message。簡要代碼以下:
class AException extends RuntimeException {
private final int code;
public AException(int code, String msg) {
super(msg);
this.code = code;
}
}
@RunWith(PowerMockRunner.class)
public class MockExceptionTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void mockException() {
thrown.expect(AException.class);
thrown.expectMessage("expected message");
// test code
}
}
複製代碼
mock一個空方法,比較簡單,就是調用doNothing().when()...
。
若是要Mock靜態方法,首先在類的開頭增長註解:@PrepareForTest({ClassNameA.class})
。
在須要Mock類方法的以前,增長代碼:PowerMockito.mockStatic(ClassNameA.class);
,而後就能夠愉快的Mock了。簡要代碼以下:
class ClassNameA {
public static int methodA() {
// code
return ret;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassNameA.class})
public class MockStaticClassTest {
@Test
public void mockStaticMethod() {
PowerMockito.mockStatic(ClassNameA.class);
Mockito.when(ClassNameA.methodA()).thenReturn(1);
// test code
}
}
複製代碼
對於某些場景,在一個單元測試裏,須要某個方法Mock,某個方法走正常邏輯,這種操做就一點要啓動容器,目前還沒找到合適的方法能夠進行這種操做,若是有更好的方法麻煩指點指點。筆者目前的作法是將原來的方法再拆分,拆分爲更小的單元,讓各自能夠進行Mock,在集成測試時才真正執行所有代碼。
以上是筆者在平常開發中遇到的場景
單元測試是針對代碼邏輯最小單元進行正確性檢驗的校驗工做,寫好單元測試,對於發現代碼bug、保障系統穩定性以及重構而言都是很是必要的一項工做,能夠提早發現一些隱藏問題。
JUnit最佳實踐這篇文章提到,Mock全部外部服務和狀態:
Mock out all external services and state Otherwise, behavior in those external services overlaps multiple tests, and state data means that different unit tests can influence each other’s outcome. You’ve definitely taken a wrong turn if you have to run your tests in a specific order, or if they only work when your database or network connection is active.
Also, this is important because you would not love to debug the test cases which are actually failing due to bugs in some external system.
因此,仍是儘量使用Mock來進行有外部服務的單元測試。
原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。
若是本文對你有幫助,麻煩點個贊,謝謝
更多精彩內容,請關注我的公衆號。