一文教會你mock(Mockito和PowerMock雙劍合璧)

語言: CN / TW / HK
作者:京東物流 楊建民

1.什麼是Mock

Mock有模仿、偽造的含義。Mock測試就是在測試過程中,對於某些不容易構造或者不容易獲取的對象,用一個虛擬的對象來創建以便測試的測試方法。mock工具使用範疇:

  • 真實對象具有不確定的行為,產生不可預測的效果。
  • 真實對象很難被創建。
  • 真實對象的某些行為很難被觸發。
  • 真實對象實際上還不存在。

MockIto和PowerMock是眾多Mock框架中的兩種,類似的還有:JMock,EasyMock,大多 Java Mock 庫如 EasyMock 或 JMock 都是 expect-run-verify (期望-運行-驗證)方式,而 Mockito 則使用更簡單,更直觀的方法:在執行後的互動中提問。使用 Mockito,你可以驗證任何你想要的。而那些使用 expect-run-verify 方式的庫,你常常被迫查看無關的交互。非 expect-run-verify 方式 也意味着,Mockito無需準備昂貴的前期啟動。他們的目標是透明的,讓開發人員專注於測試選定的行為。

2.解決的問題

我們在寫單元測試時,總會遇到類似這些問題:

1. 構造的入參,對於極值、異常邊界場景不好復現,相關的邏輯測不到,只能依靠測試環境或預發跑,運氣不好可能要改好幾次代碼重啟機器驗證,費時費力;

2. 依賴別人接口,可能需要別人協助測試環境數據庫插數才能跑通;

3. 依賴的別人的接口還沒有開發完,為了不影響提測,如何完成單元測試?

4. 編寫的單元測試依賴測試數據庫的數據,每次跑都要數據庫改數?

5. 對service層加了邏輯,跑單元測試本地驗證的時候,由於種種原因,本地環境跑不起來,折騰半天跑起來驗證完了,下次開發需求又遇到了另一個問題本地環境啟動報錯???

6. 我就想dubug到某一行代碼,但是邏輯複雜,東拼西湊的參數就是走不到,自己看代碼邏輯還要去問別人接口的返回值邏輯??(未完待續……)引入Mockito和PowerMock使得編寫單元測試更輕鬆,更省時,更省力。

3.如何解決問題

3.1  使用mock的意義 

簡單説就是無論誰的本地環境,無論判斷條件多麼苛刻,無論本地數據庫的測試數據被誰刪了改了,無論別人接口的返回值邏輯多複雜,無論自己代碼邏輯多複雜,都能獨立的、可重複執行的、行級別覆蓋的單元測試用例。

​3.2 Mockito和PowerMock   

一句話説Mockito和PowerMock。當所測邏輯裏有靜態工具類方法或私有方法我們希望他返回特定值時(極值邊界、異常測試場景),我們要用到PowerMock去彌補Mockito的不足,除此之外,用Mockito去寫單測能完成我們日常任務95%的場景。

3.3  使用Mcokito和PowerMock的最佳實踐

3.3.1  引入pom文件

3.3.2  Mockito和PowerMock 兩條通用語法  

打樁:

when(XXxService.xxMethod("期望入參")).thenReturn("期望出參"); 驗證:verify(XXxService).xxMethod("期望入參");

4.舉例説明

4.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還有一些特性可以自行學習如:

5.遇到的一些問題及解決

  • 打樁邏輯判斷是通過equals方法判斷的
  • 測試的預期是拋出異常直接在註解上加:@Test(expected=BusException.class)
  • 模擬的參數為null:Mockito.isNull() 
  • PowerMock mock靜態和私有final會有一些格式區別
  • PowerMockmock靜態方法時也可以使用spy的方式使代碼更優雅
  • mock中發現,mock沒有生效,可以嘗試升級Mockito版本解決,另外與junit反射工具類結合使用,效果更佳。
  • 涉及多層嵌套的使用場景,讀者先思考”單元“選取是否合理,多層嵌套場景將@InjectMocks和@Spy(或@Mock)聯合使用即可

結束語:

文章寫於早些時候,目前有些較新技術湧入,如:Spock、TestableMock等,但上述技術依然適用於大型系統質量內建,讀者可根據自身情況選擇性選用。​