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