Java单元测试之Mock框架
- 一、引言
- 二、為什么要用Mock
- 三、Mock使用場景
- 四、Mock定義
- 五、Mock框架
- 五、Mockito
- 5.1 Mockito基本使用
- 5.2 MockMVC測試
- 5.2.1 初始化MockMvc對象
- 5.2.2 接口測試
- 5.2.3 常用API
一、引言
實際工作中,可能會遇到如下情況:
- 場景一:依賴接口不通,甲開發(fā)A模塊,乙開發(fā)B模塊,甲的進(jìn)度比乙快,但A模塊的方法依賴于B模塊,要測試A模塊接口怎么辦?
- 場景二:異常數(shù)據(jù)難模擬,當(dāng)需要測試接口一些異常數(shù)據(jù),接口正常情況是否無法提供異常數(shù)據(jù)的。那么如何簡便地構(gòu)造接口的異常數(shù)據(jù)?
- 場景三:依賴接口性能參數(shù)無法保障。在對接口性能壓測的時候,需要下游接口及時返回數(shù)據(jù),滿足上游接口的調(diào)用頻度。在依賴接口多的情況下,如何減輕工作量?
二、為什么要用Mock
-
團(tuán)隊可以并行工作
有了Mock,前后端人員只需要定義好接口文檔就可以開始并行工作,互不影響,只在最后的聯(lián)調(diào)階段往來密切;后端與后端之間如果有接口耦合,也同樣能被Mock解決;測試過程中如果遇到依賴接口沒有準(zhǔn)備好,同樣可以借助Mock;不會出現(xiàn)一個團(tuán)隊等待另一個團(tuán)隊的情況。這樣的話,開發(fā)自測階段就可以及早開展,從而發(fā)現(xiàn)缺陷的時機(jī)也提前了,有利于整個產(chǎn)品質(zhì)量以及進(jìn)度的保證。 -
開啟TDD模式,即測試驅(qū)動開發(fā)
單元測試是TDD實現(xiàn)的基石,而TDD經(jīng)常會碰到協(xié)同模塊尚未開發(fā)完成的情況,但是有了mock,這些一切都不是問題。當(dāng)接口定義好后,測試人員就可以創(chuàng)建一個Mock,把接口添加到自動化測試環(huán)境,提前創(chuàng)建測試。 -
可以模擬那些無法訪問的資源
比如說,你需要調(diào)用一個“墻”外的資源來方便自己調(diào)試,就可以自己Mock一個。 -
隔離系統(tǒng)
假如我們需要調(diào)用一個post請求,為了獲得某個響應(yīng),來看當(dāng)前系統(tǒng)是否能正確處理返回的“響應(yīng)”,但是這個post請求會造成數(shù)據(jù)庫中數(shù)據(jù)的污染,那么就可以充分利用Mock,構(gòu)造一個虛擬的post請求,我們給他指定返回就好了 -
可以用來演示
假如我們需要創(chuàng)建一個演示程序,并且做了簡單的UI,那么在完全沒有開發(fā)后端服務(wù)的情況下,也可以進(jìn)行演示。說到演示了,假如你已經(jīng)做好了一個系統(tǒng),并且需要給客戶進(jìn)行演示,但是里面有些真實數(shù)據(jù)并不想讓用戶看到,那么同樣,你可以用Mock接口把這些敏感信息接口全部替換。 -
測試覆蓋度
假如有一個接口,有100個不同類型的返回,我們需要測試它在不同返回下,系統(tǒng)是否能夠正常響應(yīng),但是有些返回在正常情況下基本不會發(fā)生,難道你要千方百計地給系統(tǒng)做各種手腳讓他返回以便測試嗎?比如,我們需要測試在當(dāng)接口發(fā)生500錯誤的時候,app是否崩潰,別告訴我你一定要給服務(wù)端代碼做些手腳讓他返回500 。。。而使用mock,這一切就都好辦了,想要什么返回就模擬什么返回,再也不用擔(dān)心測試覆蓋度了
mock有利也有弊:
不要過度使用mock。測試用例中,掌握好使用mock的度。在涉及到網(wǎng)絡(luò)訪問、數(shù)據(jù)庫讀寫、操作系統(tǒng)交互等系統(tǒng)級調(diào)用,并且調(diào)用結(jié)果對SUT的處理邏輯有顯著影響時,優(yōu)先使用mock。
不要過度依賴基于mock的測試結(jié)果。基于mock的測試無論如何充分,都不能保證不發(fā)生問題遺漏。一個完整的測試策略,一定是由基于mock的測試和基于非mock的測試共同組成的,二者相輔相成,缺一不可。
三、Mock使用場景
四、Mock定義
mock是在測試過程中,對于一些不容易構(gòu)造/獲取的對象,創(chuàng)建一個mock對象來模擬對象的行為。比如說你需要調(diào)用B服務(wù),可是B服務(wù)還沒有開發(fā)完成,那么你就可以將調(diào)用B服務(wù)的那部分給Mock掉,并編寫你想要的返回結(jié)果。 Mock有很多的實現(xiàn)框架,例如Mockito、EasyMock、Jmockit、PowerMock、Spock等等,SpringBoot默認(rèn)的Mock框架是Mockito,和junit一樣,只需要依賴spring-boot-starter-test就可以了。
五、Mock框架
常用的mock框架有EasyMock、JMock、Mockito、PowerMockito,比較常用的是Mockito。
五、Mockito
Mockito是mocking框架,它讓你用簡潔的API做測試。而且Mockito簡單易學(xué),它可讀性強(qiáng)和驗證語法簡潔。
Mockito是GitHub上使用最廣泛的Mock框架,并與JUnit結(jié)合使用。
Mockito框架可以創(chuàng)建和配置mock對象。
使用Mockito簡化了具有外部依賴的類的測試開發(fā)。
5.1 Mockito基本使用
1、導(dǎo)入依賴
dependencies {// ... more entriestestCompile 'junit:junit:4.12'// required if you want to use Mockito for unit teststestCompile 'org.mockito:mockito-core:2.7.22'// required if you want to use Mockito for Android testsandroidTestCompile 'org.mockito:mockito-android:2.7.22' }2、靜態(tài)導(dǎo)入
import static org.mockito.Mockito.*;//示例 //創(chuàng)建mock對象,mock一個List接口 List mockedList = mock(List.class); //如果不使用靜態(tài)導(dǎo)入,則必須使用Mockito調(diào)用 List mockList = Mockito.mock(List.class);3、驗證
一旦mock對象被創(chuàng)建了,mock對象會記住所有的交互。然后你就可以選擇性的驗證感興趣的交互。
4、做測試樁
//測試樁 when(mockedList.get(0)).thenReturn("first"); when(mockedList.get(1)).thenThrow(new RuntimeException());當(dāng)調(diào)用mockList.get(0)的時候,返回first 當(dāng)調(diào)用mockList.get(1)的時候,拋出一個運行時異常其他的使用方法見中文官方文檔:Mockito 中文文檔 ( 2.0.26 beta )
5.2 MockMVC測試
對于前后端分離的項目而言,無法直接從前端靜態(tài)代碼中測試接口的正確性,因此可以通過MockMVC來模擬HTTP請求。基于RESTful風(fēng)格的SpringMVC的測試,可以測試完整的Spring MVC流程,即從URL請求到控制器處理,再到視圖渲染都可以測試。
5.2.1 初始化MockMvc對象
@Autowired private WebApplicationContext webApplicationContext; private MockMvc mockMvc;//在每個測試方法執(zhí)行之前都初始化MockMvc對象 @BeforeEach public void setupMockMvc() {mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); }5.2.2 接口測試
1、Controller層
/*** id:\\d+只匹配數(shù)字* @param id* @return*/ @GetMapping("/user/{id:\\d+}") public User getUserById(@PathVariable Long id) {return userService.getById(id); }2、Mockito構(gòu)建自定義返回結(jié)果
userService.getById并沒有返回結(jié)果,但是我們的測試并不關(guān)心userService.getById這個方法是否正常,只是在我們的測試中需要用到這個方法,所以我們可以Mock掉UserService的getById方法,自己定義返回的結(jié)果。
3、傳參數(shù)
4、期望返回結(jié)果集有兩個元素
@Test void getAll() throws Exception {User user = new User();user.setNickname("yunqing");List<User> list = new LinkedList<>();list.add(user);list.add(user);//Mock一個結(jié)果,當(dāng)userService調(diào)用list的時候,返回userwhen(userService.list()).thenReturn(list);//perform,執(zhí)行一個RequestBuilders請求,會自動執(zhí)行SpringMVC的流程并映射到相應(yīng)的控制器執(zhí)行處理mockMvc.perform(MockMvcRequestBuilders//構(gòu)造一個get請求.get("/user/list")//請求類型 json.contentType(MediaType.APPLICATION_JSON))// 期望的結(jié)果狀態(tài) 200.andExpect(MockMvcResultMatchers.status().isOk())//期望返回的結(jié)果集合有兩個元素.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2))//添加ResultHandler結(jié)果處理器,比如調(diào)試時 打印結(jié)果(print方法)到控制臺.andDo(MockMvcResultHandlers.print()); }5、測試Post請求
@Test void insert() throws Exception {User user = new User();user.setNickname("yunqing");String jsonResult = JSONObject.toJSONString(user);//直接自定義save返回truewhen(userService.save(any())).thenReturn(true);// perform : 執(zhí)行請求 ;MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders//MockMvcRequestBuilders.post("/url") : 構(gòu)造一個post請求.post("/user/insert").accept(MediaType.APPLICATION_JSON)//傳參,因為后端是@RequestBody所以這里直接傳json字符串.content(jsonResult)// 請求type : json.contentType(MediaType.APPLICATION_JSON))// 期望的結(jié)果狀態(tài) 200.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();//返回結(jié)果int statusCode = mvcResult.getResponse().getStatus();String result = mvcResult.getResponse().getContentAsString();//單個斷言Assertions.assertEquals(200, statusCode);//多個斷言,即使出錯也會檢查所有斷言assertAll("斷言",() -> assertEquals(200, statusCode),() -> assertTrue("true".equals(result)));5.2.3 常用API
常用的期望
//使用jsonPaht驗證返回的json中code、message字段的返回值 .andExpect(MockMvcResultMatchers.jsonPath("$.code").value("00000")) .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("成功")) //body屬性不為空 .andExpect(MockMvcResultMatchers.jsonPath("$.body").isNotEmpty()) // 期望的返回結(jié)果集合有2個元素 , $: 返回結(jié)果 .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(2));RequestBuilder/MockMvcRequestBuilders
//根據(jù)uri模板和uri變量值得到一個GET請求方式的MockHttpServletRequestBuilder; MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) //同get類似,但是是POST方法; MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) //同get類似,但是是PUT方法; MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) //同get類似,但是是DELETE方法; MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) //同get類似,但是是OPTIONS方法; MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables) //提供自己的Http請求方法及uri模板和uri變量,如上API都是委托給這個API; MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) //提供文件上傳方式的請求,得到MockMultipartHttpServletRequestBuilder; MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) //創(chuàng)建一個從啟動異步處理的請求的MvcResult進(jìn)行異步分派的RequestBuilder; RequestBuilder asyncDispatch(final MvcResult mvcResult)MockHttpServletRequestBuilder
//:添加頭信息; MockHttpServletRequestBuilder header(String name, Object... values)/MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) //:指定請求的contentType頭信息; MockHttpServletRequestBuilder contentType(MediaType mediaType) //:指定請求的Accept頭信息; MockHttpServletRequestBuilder accept(MediaType... mediaTypes)/MockHttpServletRequestBuilder accept(String... mediaTypes) //:指定請求Body體內(nèi)容; MockHttpServletRequestBuilder content(byte[] content)/MockHttpServletRequestBuilder content(String content) //:請求傳入?yún)?shù) MockHttpServletRequestBuilder param(String name,String... values) //:指定請求的Cookie; MockHttpServletRequestBuilder cookie(Cookie... cookies) //:指定請求的Locale; MockHttpServletRequestBuilder locale(Locale locale) //:指定請求字符編碼; MockHttpServletRequestBuilder characterEncoding(String encoding) //:設(shè)置請求屬性數(shù)據(jù); MockHttpServletRequestBuilder requestAttr(String name, Object value) //:設(shè)置請求session屬性數(shù)據(jù); MockHttpServletRequestBuilder sessionAttr(String name, Object value)/MockHttpServletRequestBuilder sessionAttrs(Map<string, object=""> sessionAttributes) //指定請求的flash信息,比如重定向后的屬性信息; MockHttpServletRequestBuilder flashAttr(String name, Object value)/MockHttpServletRequestBuilder flashAttrs(Map<string, object=""> flashAttributes) //:指定請求的Session; MockHttpServletRequestBuilder session(MockHttpSession session) // :指定請求的Principal; MockHttpServletRequestBuilder principal(Principal principal) //:指定請求的上下文路徑,必須以“/”開頭,且不能以“/”結(jié)尾; MockHttpServletRequestBuilder contextPath(String contextPath) //:請求的路徑信息,必須以“/”開頭; MockHttpServletRequestBuilder pathInfo(String pathInfo) //:請求是否使用安全通道; MockHttpServletRequestBuilder secure(boolean secure) //:請求的后處理器,用于自定義一些請求處理的擴(kuò)展點; MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor)MockMultipartHttpServletRequestBuilder
//:指定要上傳的文件; MockMultipartHttpServletRequestBuilder file(String name, byte[] content)/MockMultipartHttpServletRequestBuilder file(MockMultipartFile file)ResultActions
//:添加驗證斷言來判斷執(zhí)行請求后的結(jié)果是否是預(yù)期的; ResultActions andExpect(ResultMatcher matcher) //:添加結(jié)果處理器,用于對驗證成功后執(zhí)行的動作,如輸出下請求/結(jié)果信息用于調(diào)試; ResultActions andDo(ResultHandler handler) //:返回驗證成功后的MvcResult;用于自定義驗證/下一步的異步處理; MvcResult andReturn()ResultMatcher/MockMvcResultMatchers
//:請求的Handler驗證器,比如驗證處理器類型/方法名;此處的Handler其實就是處理請求的控制器; HandlerResultMatchers handler() //:得到RequestResultMatchers驗證器; RequestResultMatchers request() //:得到模型驗證器; ModelResultMatchers model() //:得到視圖驗證器; ViewResultMatchers view() //:得到Flash屬性驗證; FlashAttributeResultMatchers flash() //:得到響應(yīng)狀態(tài)驗證器; StatusResultMatchers status() //:得到響應(yīng)Header驗證器; HeaderResultMatchers header() //:得到響應(yīng)Cookie驗證器; CookieResultMatchers cookie() //:得到響應(yīng)內(nèi)容驗證器; ContentResultMatchers content() //:得到Json表達(dá)式驗證器; JsonPathResultMatchers jsonPath(String expression, Object ... args)/ResultMatcher jsonPath(String expression, Matcher matcher) //:得到Xpath表達(dá)式驗證器; XpathResultMatchers xpath(String expression, Object... args)/XpathResultMatchers xpath(String expression, Map<string, string=""> namespaces, Object... args) //:驗證處理完請求后轉(zhuǎn)發(fā)的url(絕對匹配); ResultMatcher forwardedUrl(final String expectedUrl) //:驗證處理完請求后轉(zhuǎn)發(fā)的url(Ant風(fēng)格模式匹配,@since spring4); ResultMatcher forwardedUrlPattern(final String urlPattern) //:驗證處理完請求后重定向的url(絕對匹配); ResultMatcher redirectedUrl(final String expectedUrl) //:驗證處理完請求后重定向的url(Ant風(fēng)格模式匹配,@since spring4); ResultMatcher redirectedUrlPattern(final String expectedUrl)總結(jié)
以上是生活随笔為你收集整理的Java单元测试之Mock框架的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用.Net5尝鲜的一些小总结及Conf
- 下一篇: (60)Java基础 --单元测试