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

2023-01-05 12:01:17
作者:京東物流 楊建民

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等,但上述技術依然適用於大型系統質量內建,讀者可根據自身情況選擇性選用。​