使用 Mockito 輔助單元測試


了解過單元測試相關概念的人應該會清楚一個概念:一個好的單元測試應該是與環境無關的,每一個測試都是相互獨立的。亦即你可以在任何地方,以任意順序運行這些測試,最后得到的結果是一樣的。但是我被測試的類/方法中本身夾雜着對其它類的依賴,這又該怎么處理呢,將依賴進行 mock 是其中一個做法。本文將記錄我在測試過程中的一些備忘,以及遇到的一些問題。

背景說明

我要對我正在開發的一個考試系統中的題目管理部分進行單元測試,這部分主要有一個 SubjectService 接口及其對應的實現類 SubjectServiceImpl,Service 內部又依賴於 DAO 層的兩個 Mapper(SubjectMapperSubjectAnswerMapper)。現在我要對 Service 層進行單元測試。此為背景。

過程

首先要確定一個概念:測 Service 層,我們要測它的什么?Service 層對數據庫的訪問是通過 DAO 層進行的。那么對數據庫相關的操作就不適宜放在這里進行測試(對它們的測試應該放在 DAO 層)。Service 層作為主要業務邏輯的載體,對 Service 層的測試應該圍繞流程進行(對於不合法的輸入,應該拋出對應的異常;對於正常的輸入,則流程應該能正常走完,至於數據庫訪問的正確與否,交給 DAO 層的單元測試進行保證)。

確定了這一點之后,接下來就可以開始測試流程了。首先是引入相關的測試框架。由於項目采用了 SpringBoot,我參考了參考資料中的內容,構建起整個測試環境的依賴。

然后就是開始編寫相關的測試類:

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest { 
    private SubjectServiceImpl subjectServiceImpl;
}

對於 Service 所依賴的兩個 DAO,只需要創建對應的兩個 Mapper 並為其加上 @Mock 注解,然后在被測試對象上加上 @InjectMocks 注解,即完成了對依賴的 mock:

@RunWith(MockitoJUnitRunner.class)
public class SubjectServiceImplTest {
    @Mock
    private SubjectMapper subjectMapper;
    
    @Mock
    private SubjectAnswerMapper subjectAnswerMapper;
    
    @InjectMocks
    private SubjectServiceImpl subjectServiceImpl;
}

然后就可以開始測試我們的 Service 的方法了。由於 Mock 的引入,現在測試方法的整個流程變成了 4 個步驟:

  1. 准備測試用的輸入
  2. 給 Mock 對象設置預期的輸出(因為被測對象所依賴的是由你虛擬出來的東西,所以依賴應該怎么響應需要你手動設置)
  3. 運行被測方法
  4. 檢查運行結果是否與預期一致

以下是一個例子

/**
     * 測試插入沒有答案的試題
     * 應該拋出異常
     */
@Test
public void testSaveSubjectWithoutAnswer() {
    SubjectDTO subjectDTO = new SubjectDTO();
    subjectDTO.setName("testSubject");
    subjectDTO.setDifficulty(1L);
    subjectDTO.setCategoryId(1L);
    subjectDTO.setSubjectTypeId(1L);

    Mockito.when(subjectMapper.insert(Mockito.any()))
        .thenReturn(1);

    try {
        subjectService.saveSubject(subjectDTO);
    } catch (BusinessException e) {
        assertEquals(e.getCode(), ResultEnum.INCOMPLETE_ADD_EXERCISE_INFORMATION.getCode());
        return;
    }
    throw new RuntimeException("Should not reach here!");
}

在上面的例子中,步驟 2 使用到了 Mockito 類的一些靜態方法,設定了 Mapper 里會被調用方法的響應。(這里建議為了簡化代碼,可以通過 import static 的方式引入 Mockito 的所有方法,這樣可以省略前面的類名)受限於我使用的 JUnit 為 JUnit 4,所以對異常的測試只能這樣進行,在 JUnit 5 中就添加了對預期拋出異常的 assert。

會做這個測試,其余的測試也就基本能夠進行下去了。

遇到的問題

在跑的過程中,我發現了一個挺棘手的問題,目前還沒找到合適的方案。

項目的 DAO 層使用的是 MyBatis + 通用 Mapper 這一套框架。我在 Mock 方法的時候發現在運行的過程中,有關 Mapper 方法中的 selectByExample 的部分總是運行不了,我在方法內部寫了創建 Example 的過程,如果使用 Mock,創建 Example 的過程會出現異常,內容大概是要依賴一個數據庫環境。所以在不考慮 Service 和 DAO 集成測試的情況下,涉及到這部分的 Service 的部分無法進行測試,后續我會繼續查閱相關資料並更新此文。

參考資料

SpringBoot2.x 單元測試 | 閃爍之狐


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM