Java-Mock簡化單元測試

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單元測試目的","attrs":{}},{"type":"link","attrs":{"href":"#toc_1","title":null}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"維基百科對單元測試的定義:\n\n單元測試(英語:Unit Testing)又稱爲模塊測試,是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。\n\n單元測試的目標是隔離程序部件並證明這些單個部件是正確的。\n","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"畫外音:單元測試是比較細粒度的測試,是對接口、方法、函數的測試,目的是保障代碼按照正確的方式去執行,提高代碼質量。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"單元測試實施原則","attrs":{}},{"type":"link","attrs":{"href":"#toc_2","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mock脫離數據庫 + 不啓動Spring + 優化測試速度 + 不引入項目組件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單元測試不應該依賴數據,依賴外部服務或組件等,會對其他數據產生影響的情況。啓動Spring容器,一般比較慢,可能會啓動消息監聽消費消息,定時任務的執行等,對數據產生影響。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mock測試就是在測試過程中,對那些當前測試不關心的,不容易構建的對象,用一個虛擬對象來代替測試的情形。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說白了:就是解耦(虛擬化)要測試的目標方法中調用的其它方法,例如:Service的方法調用Mapper類的方法,這時候就要把Mapper類Mock掉(產生一個虛擬對象),這樣我們可以自由的控制這個Mapper類中的方法,讓它們返回想要的結果、拋出指定異常、驗證方法的調用次數等等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"減少單元測試對外部的依賴和副作用,提高單元測試效率","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"不使用 @Autowired,@Resource, 需要啓動 Spring 容器,測試速度慢,會產生副作用;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"不使用 @SpringBootTest,@SpringBootTest(classes = Application.class), 這會啓動整個 SpringBoot 服務","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"不應調用數據庫,除非是做數據庫操作相關的測試,雖然可配置事務回滾,但大多數情況下還是會產生髒數據等問題","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"使用Assert斷言,用於判斷某個特定條件下某個方法的行爲,爲了證明某段代碼的執行結果和期望的一致","attrs":{}}]}],"attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"畫外音:單元測試應小而輕,提交測試效率,較少對外部的依賴,比如數據庫、Spring容器、網絡服務等,而只關心我們自己的代碼,通過Mock來解決對外部的依賴","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Mockito的使用","attrs":{}},{"type":"link","attrs":{"href":"#toc_3","title":null}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"基本使用","attrs":{}},{"type":"link","attrs":{"href":"#toc_4","title":null}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用靜態方法 mock()","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用註解 @Mock 標註","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果使用@Mock註解, 必須去觸發所標註對象的創建. 可以使用 MockitoRule來實現. 它調用了靜態方法MockitoAnnotations.initMocks(this) 去初始化這個被註解標註的字段.或者也可以使用@RunWith(MockitoJUnitRunner.class).","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“when thenReturn”和”when thenThrow”","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模擬對象可以根據傳入方法中的參數來返回不同的值, when(….).thenReturn(….)方法是用來根據特定的參數來返回特定的值.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們也可以使用像 anyString 或者 anyInt anyLong any 這樣的方法來定義某個依賴數據類型的方法返回特定的值.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"“doReturn when” 和 “doThrow when”","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"doReturn(…).when(…)的方法調用和when(….).thenReturn(….)類似.對於調用過程中拋出的異常非常有用.而doThrow則也是它的一個變體.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"常用註解","attrs":{}},{"type":"link","attrs":{"href":"#toc_5","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@Mock:對函數的調用均執行mock(即虛假函數),不執行真正部分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@Spy:對函數的調用均執行真正部分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@InjectMocks:創建一個實例,簡單的說是這個Mock可以調用真實代碼的方法,使用@Mock(或@Spy)註解創建的mock將被注入到用該實例中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Mockito中的Mock和Spy都可用於攔截那些尚未實現或不期望被真實調用的對象和方法,併爲其設置自定義行爲。二者的區別在於Mock不真實調用,Spy會真實調用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@MockBean: 功能同 @Mock, 只是會將實例放入 Spring 容器管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@SpyBean: 功能同 @Spy, 只是會將實例放入 Spring 容器管理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Spy 和 Mock 生成的對象不受 Spring 管理","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"Spy 調用真實方法時,其它 bean 是無法注入的,要使用注入,要使用 SpyBean","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"SpyBean 和 MockBean 生成的對象受 Spring 管理,相當於自動替換對應類型 bean 的注入,比如 @Autowired、@Resource 等注入","attrs":{}}]}],"attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"最佳實踐","attrs":{}},{"type":"link","attrs":{"href":"#toc_6","title":null}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"// 不使用 @SpringBootTest(classes = Application.class)\n@RunWith(SpringRunner.class)\npublic class ExamAnswerComponentTest {\n\n // 創建一個實例,會注入Mock變量\n @InjectMocks\n private ExamAnswerComponent examAnswerComponent = new ExamAnswerComponentImpl();\n\n // 相關操作會被Mock掉\n @Mock\n private ExamAnswerCacheObjectiveDAO examAnswerCacheObjectiveDAO;\n\n @Before\n public void setUp() {\n // 初始化Mock\n MockitoAnnotations.initMocks(this);\n\n // given...willReturn 指定方法參數,模擬返回值\n given(examAnswerCacheObjectiveDAO.selectByBizIdAndPaperAndQuestion(any(), any(), any()))\n .willReturn(new ExamAnswerCacheObjectivePO());\n given(examAnswerCacheObjectiveDAO.insert(any())).willReturn(1);\n given(examAnswerCacheObjectiveDAO.updateUserAnswerById(any(), any())).willReturn(1);\n }\n\n\n @Test\n public void saveOrUpdateAnswerCacheObjective() {\n\n ExamAnswerCacheObjectivePO po = new ExamAnswerCacheObjectivePO();\n po.setBizId(100000015L);\n po.setBizType(9);\n po.setUserAnswer(\"A\");\n po.setGroupPaperId(1000320L);\n po.setQuestionId(1000042L);\n po.setQuestionType(1);\n\n int affect = examAnswerComponent.saveOrUpdateAnswerCacheObjective(po);\n System.out.println(\"affect = \" + affect);\n Assert.assertTrue(affect > 0);\n }\n\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"參考","attrs":{}},{"type":"link","attrs":{"href":"#toc_7","title":null}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.codenong.com/cs106503150/","title":null},"content":[{"type":"text","text":"https://www.codenong.com/cs106503150/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章