Mockito-提高單元測試效率利器

前言

之前在開發進行到寫單元測試階段的時候,發現要測試的方法裏面是包含依賴的:外部接口RPC調用、DB調用。在某些情況下,部分依賴不穩定或者無法在測試環境調用時,會導致用例偶爾執行失敗。

另外一點,很多用例都是在測試用例的開頭寫了@SpringRunTest的註解,導致跑用例的時候會啓動整個Spring容器,這樣一來,運行測試用例就非常慢了。當在一些比較大的項目運行用例時,甚至達到了每次啓動容器需要5-6分鐘的時長,漸漸就有點受不了這種操作,每改一行代碼心裏都焦急,因爲如果錯了的話又要再等5-6分鐘才能看到效果了。後來請教同事和上網搜索,找到了一種比較快且安全的方案,使用Mock框架—Mockito,學習並實踐了一段時間,總結一下使用方法。

Mockito

Mockito是當前最流行的單元測試Mock框架。

什麼是Mock

Mock的字面意思就是模仿,虛擬,在單元測試中,使用Mock可以虛擬出一個外部依賴對象。

對於在單元測試中一些不容易構造或者不容易獲取的對象(如外部服務),用一個Mock對象來創建,可以降低測試的複雜度,只關心當前單元測試的方法。

爲什麼用Mock

單元測試的目的就是爲了驗證一個代碼單元的正確性,真正要驗證的只是某個輸入對應的輸出的正確與否。如果把外部依賴服務引入進來,就會增加原來單元的複雜度,且在該單元中隱形地摻雜了其他功能的內容。

使用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"));
    }
}

解釋下上面代碼用到的幾個註解

@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實踐最簡單的示例,在生產環境使用過程中,會有各種各樣的需求需要滿足,下面列一下筆者遇到過的場景。

mock異常

這種場景是,方法裏面聲明瞭可能會拋出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空方法

mock一個空方法,比較簡單,就是調用doNothing().when()...

mock靜態方法

如果要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,某個方法走正常邏輯,這種操作就一點要啓動容器,目前還沒找到合適的方法可以進行這種操作,如果有更好的方法麻煩指點指點。筆者目前的做法是將原來的方法再拆分,拆分爲更小的單元,讓各自可以進行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來進行有外部服務的單元測試。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有幫助,請點個贊吧,謝謝

更多精彩內容,請關注個人公衆號。

參考文章

JUnit Best Practices Guide

https://howtodoinjava.com/best-practices/unit-testing-best-practices-junit-reference-guide

Mockito

https://site.mockito.org/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章