在日常工作中,看過(guò)很多開(kāi)發(fā)人員不寫(xiě)代碼測(cè)試,大部分理由是“太忙“或者”沒(méi)必要”,更嚴(yán)重的是很多開(kāi)發(fā)人員甚至不知道如何寫(xiě)測(cè)試代碼,本文我們總結(jié)了一位騰訊后端對(duì) Controller層代碼優(yōu)秀的測(cè)試經(jīng)驗(yàn),希望對(duì)你有幫助。

在 如何編寫(xiě)優(yōu)雅的 Controller代碼? 這篇文章中,我們分析了 Controller層的 6個(gè)主要職責(zé),本文將分析如何對(duì) Controller層進(jìn)行360度無(wú)死角測(cè)試,首先回顧下 Controller層的6個(gè)主要職責(zé):
理解了 Controller的職責(zé),才能更有目的對(duì)它進(jìn)行測(cè)試,如下代碼,包含了 6個(gè)主要職責(zé),我們?cè)撊绾尉帉?xiě)測(cè)試代碼?
@RestControllerpublic class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/user/register") public String getGradeById(@Validated @RequestBody User user) { // 調(diào)用注冊(cè)的業(yè)務(wù)方法 String userId = userService.register(user); return userId; }}public class User { @NotBlank(message = "Nickname is required.") private String nickname; private Integer age; // getters and setters and constructors}作為一名程序員,代碼質(zhì)量是我們必須守住的底線,對(duì)于自己編寫(xiě)的代碼一定需要經(jīng)過(guò)測(cè)試,而不是把測(cè)試工作不負(fù)責(zé)的全部交給測(cè)試人員。
對(duì)于開(kāi)發(fā)人員,保證代碼質(zhì)量最有效的方式是測(cè)試,最常見(jiàn)的測(cè)試方式有 2種:?jiǎn)卧獪y(cè)試(Unit Testing)和 集成測(cè)試(Integration Testing)。
單元測(cè)試的目的是驗(yàn)證單個(gè)功能單元的行為是否正確,這里的功能單元通常是一個(gè)類或方法,單元測(cè)試最大的優(yōu)點(diǎn)是可以在不依賴于其他部分的前提下測(cè)試某個(gè)功能單元。
集成測(cè)試的目的是驗(yàn)證多個(gè)功能單元之間的交互是否正確,可能涉及數(shù)據(jù)庫(kù)、文件系統(tǒng)、網(wǎng)絡(luò)等外部系統(tǒng),如果需要排除外部系統(tǒng)的干擾,可以對(duì)他們進(jìn)行 mock。
對(duì)于 Controller層的代碼測(cè)試,因?yàn)閱卧獪y(cè)試的局限性,所以我們需要采用集成測(cè)試,本文將使用SpringBootTest 和 Mockito兩個(gè)主流的測(cè)試框架進(jìn)行分析,下面先說(shuō)明幾個(gè)重要的組件:
@WebMvcTest是 Spring Boot提供的一個(gè)注解,用于測(cè)試 SpringMVC的 Controller,它能夠啟動(dòng)一個(gè)最小化的 Spring應(yīng)用上下文,包含僅與 Web層相關(guān)的 bean,從而快速且高效地測(cè)試 Controller的功能。
@WebMvcTest主要功能如下:
MockMvc是 Spring框架中用于測(cè)試 Spring MVC控制器的主要工具,它允許開(kāi)發(fā)者在不啟動(dòng)整個(gè) HTTP服務(wù)器的情況下,測(cè)試控制器的請(qǐng)求處理方法。MockMvc 提供了一種方式來(lái)模擬 HTTP請(qǐng)求并驗(yàn)證響應(yīng),確保控制器行為符合預(yù)期。
MockMvc的主要功能如下:
Mockito 是一個(gè)流行的 Java測(cè)試框架,用于創(chuàng)建和配置 mock 對(duì)象。它主要用于單元測(cè)試中,通過(guò)模擬依賴對(duì)象的行為,使得測(cè)試目標(biāo)對(duì)象能夠獨(dú)立于其依賴項(xiàng)進(jìn)行測(cè)試。
Mockito主要功能如下:
它是 Jackson庫(kù)中的核心類之一,用于將 Java 對(duì)象轉(zhuǎn)換為 JSON 字符串,或者將 JSON 字符串轉(zhuǎn)換為 Java 對(duì)象。在 Spring 應(yīng)用中,ObjectMapper 被廣泛用于處理 JSON 數(shù)據(jù)。
ObjectMapper的主要功能如下:
所以,一個(gè)包含上述所有組件的完整代碼示例如下:
@WebMvcTest(controllers = RegisterRestController.class)class RegisterRestControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private XXX xxx; // 用戶業(yè)務(wù)的bean @Test void test() throws Exception { mockMvc.perform(...); }}介紹了上面幾個(gè)集成測(cè)試的組件之后,接下來(lái)我們就可以詳解的分析他們是如何應(yīng)用到 Controller的集成測(cè)試中。
我們按照 Controller的 6個(gè)主要職責(zé)來(lái)分別講解他們是如何進(jìn)行測(cè)試的。
Controller是接收請(qǐng)求的入口,因此對(duì)于 Controller接收 HTTP(s)請(qǐng)求驗(yàn)證 Controller是否偵聽(tīng)某個(gè) HTTP 請(qǐng)求非常簡(jiǎn)單,我們只需調(diào)用 MockMvc.perform() 方法,返回 200代表成功,返回非200就代表異常,示例代碼如下:
@Testvoid whenReceiveHttpRqe_thenReturns200() throws Exception { mockMvc.perform(post("/user/register") .contentType("application/json")) .andExpect(status().isOk()); }Controller是通過(guò)@PathVariable、@RequestBody、@RequestParam 3種方式來(lái)接收參數(shù)的,因此,下面的示例代碼分別模擬這 3種方式是如何進(jìn)行參數(shù)傳遞的。
@Autowiredprivate ObjectMapper objectMapper;@Testvoid whenValidInput_thenReturns200() throws Exception { User user = new User("zhangsan", 21); mockMvc.perform(post("/user/register/{id}", 1111) // 模擬通過(guò) @PathVariable傳遞參數(shù) .contentType("application/json") .param("name", "張三") // 模擬通過(guò) @RequestParam傳遞參數(shù) .content(objectMapper.writeValueAsString(user))) // 模擬通過(guò) @RequestBody傳遞參數(shù) .andExpect(status().isOk());}通過(guò)上面的方式,我們模擬了一個(gè)正常的 HTTP請(qǐng)求,并且使用 3種方式進(jìn)行參數(shù)傳遞。
測(cè)試參數(shù)的校驗(yàn),主要是為了檢查代碼邏輯有沒(méi)有對(duì)參數(shù)進(jìn)行有效的驗(yàn)證,比如,必填字段判空,字符串最大長(zhǎng)度限制,數(shù)字最大值和最小值校驗(yàn),手機(jī)號(hào)或郵箱格式校驗(yàn)等。
參數(shù)校驗(yàn)測(cè)試一般分正常測(cè)試和按預(yù)期失敗的異常測(cè)試,特別需要進(jìn)行邊界的測(cè)試。如下示例,給出了測(cè)試通過(guò)和符合預(yù)期并返回 400的 BadRequest:
public class User { @NotBlank(message = "Nickname is required.") private String nickname; private Integer age; // getters and setters and constructors}@Testvoid whenNicknameNull_thenReturns400() throws Exception { User user = new User(null, 21); // 當(dāng) nickname為空時(shí),會(huì)拋出 400的 BadRequest異常 mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isOk());}@Testvoid testReturns200() throws Exception { User user = new User("zhangsan", 21); mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isBadRequest());}對(duì)于業(yè)務(wù)方法調(diào)用的測(cè)試,通常我們會(huì)使用Mockito.mock來(lái)模擬業(yè)務(wù)方法的返回值,而業(yè)務(wù)方式的真實(shí)運(yùn)行邏輯會(huì)在 Server的單元測(cè)試中完成,如下示例代碼:
// 調(diào)用注冊(cè)的業(yè)務(wù)方法String userId = userService.register(user);@Testvoid whenLogic_thenReturnsExceptData() throws Exception { User user = new User(null, 21); // mock userService.register(user)返回值為 userId Mockito.when(userService.register(user)).thenReturn("userId"); mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isOk()) .andExpect(jsonPath("$.data", is("userId")));}在業(yè)務(wù)代碼執(zhí)行完之后,我們需要對(duì) HTTP對(duì)應(yīng)響應(yīng),因此可以使用 andReturn()方法將 HTTP交互的結(jié)果存儲(chǔ)在 MvcResult類型的變量中,然后從響應(yīng)正文中讀取 JSON字符串,并使用 isEqualToIgnoringWhitespace()將其與預(yù)期字符串進(jìn)行比較,如下示例:
// 調(diào)用注冊(cè)的業(yè)務(wù)方法String userId = userService.register(user);@Testvoid whenLogic_thenReturnsExceptData() throws Exception { User user = new User("zhangsan", 21); // mock userService.register(user)返回值為 userId Mockito.when(userService.register(user)).thenReturn("userId"); MvcResult mvcResult = mockMvc.perform(post("/user/register") .content(objectMapper.writeValueAsString(user))) .andReturn(); //將結(jié)果返回 String expectedResponse = "userId"; String actualResponseBody = mvcResult.getResponse().getContentAsString(); assertThat(actualResponseBody).isEqualToIgnoringWhitespace( objectMapper.writeValueAsString(expectedResponse));}測(cè)試異是指如果發(fā)生異常,Controller應(yīng)返回特定的 HTTP狀態(tài),比如 200,400,500等等, 默認(rèn)情況下,Spring 會(huì)處理其中的大多數(shù)情況。但是,如果我們有一個(gè)自定義異常處理,我們想要測(cè)試它。假設(shè)我們要返回一個(gè)結(jié)構(gòu)化的 JSON 錯(cuò)誤響應(yīng),其中包含請(qǐng)求中每個(gè)無(wú)效字段的字段名稱和錯(cuò)誤消息。我們會(huì)創(chuàng)建一個(gè)這樣的@ControllerAdvice:
@Testvoid whenNullValue_thenReturns400AndErrorResult() throws Exception { User user = new User("zhangsan", 21); MvcResult mvcResult = mockMvc.perform(post("/user/register") .contentType("application/json") .content(objectMapper.writeValueAsString(user))) .andExpect(status().isBadRequest()) .andReturn(); ErrorResult expectedErrorResponse = new ErrorResult("Nickname", "Nickname is required."); String actualResponse = mvcResult.getResponse().getContentAsString(); String expectedResponseBody = objectMapper.writeValueAsString(expectedErrorResponse); assertThat(actualResponse).isEqualToIgnoringWhitespace(expectedErrorResponse);}本文結(jié)合 SpringBootTest 和 Mockito兩個(gè)主流的測(cè)試框架,對(duì) Controller各個(gè)職責(zé)進(jìn)行全面的測(cè)試,并且給出了比較詳細(xì)的示例代碼,通過(guò)本文的分析,我們不僅可以學(xué)會(huì)對(duì) Controller的測(cè)試,同時(shí)還應(yīng)該觸類旁通,將里面優(yōu)秀的思維應(yīng)用到其他層級(jí)代碼的測(cè)試。
代碼質(zhì)量是開(kāi)發(fā)人員必須守住的底線,所以在日常的開(kāi)發(fā)中一定要秉著對(duì)自己負(fù)責(zé)的態(tài)度,采用單元測(cè)試和集成測(cè)試對(duì)自己的代碼進(jìn)行測(cè)試。
本文鏈接:http://m.www897cc.com/showinfo-26-96427-0.html新來(lái)的騰訊后端,直接定義代碼測(cè)試新姿勢(shì)!
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: 2024 Vue 最全的生態(tài)工具組合推薦指南
下一篇: CVPR 2024 | PICO 交互感知團(tuán)隊(duì) - 從圖像到可驅(qū)動(dòng)形象:OHTA 實(shí)現(xiàn)單圖創(chuàng)建手部化身