1.解決的問題
我們在寫單元測試時,總會遇到類似這些問題:
1. 構造的入參,對於極值、異常邊界場景不好復現,相關的邏輯測不到,只能依靠測試環境或預發跑,運氣不好可能要改好幾次代碼重啓機器驗證,費時費力;
2. 依賴別人接口,可能需要別人協助測試環境數據庫插數才能跑通;
3. 依賴的別人的接口還沒有開發完,爲了不影響提測,如何完成單元測試?
4. 編寫的單元測試依賴測試數據庫的數據,每次跑都要數據庫改數?
5. 對service層加了邏輯,跑單元測試本地驗證的時候,由於種種原因,本地環境跑不起來,折騰半天跑起來驗證完了,下次開發需求又遇到了另一個問題本地環境啓動報錯???
6. 我就想dubug到某一行代碼,但是邏輯複雜,東拼西湊的參數就是走不到,自己看代碼邏輯還要去問別人接口的返回值邏輯??(未完待續……)
引入Mockito和PowerMock使得編寫單元測試更輕鬆,更省時,更省力。
2.如何解決問題
2.1 使用mock的意義
使我們的單測滿足AIR原則:Automatic(自動化)、Independent(獨立性)、Repeatable(可重複)
簡單說就是無論誰的本地環境,無論判斷條件多麼苛刻,無論本地數據庫的測試數據被誰刪了改了,無論別人接口的返回值邏輯多複雜,無論自己代碼邏輯多複雜,都能獨立的、可重複執行的、行級別覆蓋的單元測試用例。
2.2 Mockito和PowerMock
一句話說Mockito和PowerMock。當所測邏輯裏有靜態工具類方法或私有方法我們希望他返回特定值時(極值邊界、異常測試場景),我們要用到PowerMock去彌補Mockito的不足,除此之外,用Mockito去寫單測能完成我們日常任務95%的場景。
2.3 使用Mcokito和PowerMock的最佳實踐
2.3.1 引入pom文件
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<!-- powerMock -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.6</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>objenesis</artifactId>
<groupId>org.objenesis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
2.3.2 Mockito和PowerMock 兩條通用語法
打樁:
when(XXxService.xxMethod("期望入參")).thenReturn("期望出參");
驗證:
verify(XXxService).xxMethod("期望入參");
3.舉例說明
3.1 SpringBoot項目下Mockito和PowerMock最佳實踐
- classes: 指定要加載的類
- properties: 指定要設置屬性
- @InjectMocks: 需要注入mock對象的Bean
- @MockBean或@Mock: 需要mock的Bean
import X;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
/**
* 測試類A,調用服務B和一個靜態工具類X
*/
@RunWith(PowerMockRunner.class)
@SpringBootTest(classes = {
A.class
})
@PowerMockIgnore({"javax.management.*"})
@PrepareForTest({X.class}) //mock 靜態方法
public class ATest {
@InjectMocks
private A a;
@Mock
private B b;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void Test() {
when(b.someMethodB(any())).thenReturn(someThingB());
a.someMethodA(someThingA1(), someThingA2());
verify(b).someMethodB(any());
}
/**
* 異常邊界測試
*/
@Test
public void test_ExceptionTest() throws ParseException {
PowerMockito.mockStatic(X.class);
// 模擬異常拋出的場景
when(X.strToDate(anyString(), anyString())).thenThrow(ParseException.class);
when(X.convertLocalDateTime(any())).thenReturn(someThing());
when(b.someMethodB(any())).thenReturn(someThingB());
a.someThingA(someThingA1(), someThingA2());
verify(b).someMethodB(any());
}
}
優雅的mock可以考慮@spy,當然,mockito還有一些特性可以自行學習如:
4.遇到的一些問題及解決
- 打樁邏輯判斷是通過equals方法判斷的
- 測試的預期是拋出異常直接在註解上加:@Test(expected=BusException.class)
- 模擬的參數爲null:Mockito.isNull()
- PowerMock mock靜態和私有final會有一些格式區別
- PowerMock mock靜態方法時也可以使用spy的方式使代碼更優雅
- mock中發現,mock沒有生效,可以嘗試升級Mockito版本解決,另外可以於junit反射工具類結合使用,效果更佳。