瞭解過單元測試相關概念的人應該會清楚一個概念:一個好的單元測試應該是與環境無關的,每個測試都是相互獨立的。亦即你能夠在任何地方,以任意順序運行這些測試,最後獲得的結果是同樣的。可是我被測試的類/方法中自己夾雜着對其它類的依賴,這又該怎麼處理呢,將依賴進行 mock 是其中一個作法。本文將記錄我在測試過程當中的一些備忘,以及遇到的一些問題。java
背景說明
我要對我正在開發的一個考試系統中的題目管理部分進行單元測試,這部分主要有一個 SubjectService
接口及其對應的實現類 SubjectServiceImpl
,Service 內部又依賴於 DAO 層的兩個 Mapper(SubjectMapper
和 SubjectAnswerMapper
)。如今我要對 Service 層進行單元測試。此爲背景。git
過程
首先要肯定一個概念:測 Service 層,咱們要測它的什麼?Service 層對數據庫的訪問是經過 DAO 層進行的。那麼對數據庫相關的操做就不適宜放在這裏進行測試(對它們的測試應該放在 DAO 層)。Service 層做爲主要業務邏輯的載體,對 Service 層的測試應該圍繞流程進行(對於不合法的輸入,應該拋出對應的異常;對於正常的輸入,則流程應該能正常走完,至於數據庫訪問的正確與否,交給 DAO 層的單元測試進行保證)。github
肯定了這一點以後,接下來就能夠開始測試流程了。首先是引入相關的測試框架。因爲項目採用了 SpringBoot,我參考了參考資料中的內容,構建起整個測試環境的依賴。spring
而後就是開始編寫相關的測試類:數據庫
@RunWith(MockitoJUnitRunner.class) public class SubjectServiceImplTest { private SubjectServiceImpl subjectServiceImpl; }
對於 Service 所依賴的兩個 DAO,只須要建立對應的兩個 Mapper 併爲其加上 @Mock
註解,而後在被測試對象上加上 @InjectMocks
註解,即完成了對依賴的 mock:springboot
@RunWith(MockitoJUnitRunner.class) public class SubjectServiceImplTest { @Mock private SubjectMapper subjectMapper; @Mock private SubjectAnswerMapper subjectAnswerMapper; @InjectMocks private SubjectServiceImpl subjectServiceImpl; }
而後就能夠開始測試咱們的 Service 的方法了。因爲 Mock 的引入,如今測試方法的整個流程變成了 4 個步驟:app
- 準備測試用的輸入
- 給 Mock 對象設置預期的輸出(由於被測對象所依賴的是由你虛擬出來的東西,因此依賴應該怎麼響應須要你手動設置)
- 運行被測方法
- 檢查運行結果是否與預期一致
如下是一個例子框架
/** * 測試插入沒有答案的試題 * 應該拋出異常 */ @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 的部分沒法進行測試,後續我會繼續查閱相關資料並更新此文。