在日常工作中,看过很多开发人员不写代码测试,大部分理由是“太忙“或者”没必要”,更严重的是很多开发人员甚至不知道如何写测试代码,本文我们总结了一位腾讯后端对 Controller层代码优秀的测试经验,希望对你有帮助。
在 如何编写优雅的 Controller代码? 这篇文章中,我们分析了 Controller层的 6个主要职责,本文将分析如何对 Controller层进行360度无死角测试,首先回顾下 Controller层的6个主要职责:
理解了 Controller的职责,才能更有目的对它进行测试,如下代码,包含了 6个主要职责,我们该如何编写测试代码?
@RestControllerpublic class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/user/register") public String getGradeById(@Validated @RequestBody User user) { // 调用注册的业务方法 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}
作为一名程序员,代码质量是我们必须守住的底线,对于自己编写的代码一定需要经过测试,而不是把测试工作不负责的全部交给测试人员。
对于开发人员,保证代码质量最有效的方式是测试,最常见的测试方式有 2种:单元测试(Unit Testing)和 集成测试(Integration Testing)。
单元测试的目的是验证单个功能单元的行为是否正确,这里的功能单元通常是一个类或方法,单元测试最大的优点是可以在不依赖于其他部分的前提下测试某个功能单元。
集成测试的目的是验证多个功能单元之间的交互是否正确,可能涉及数据库、文件系统、网络等外部系统,如果需要排除外部系统的干扰,可以对他们进行 mock。
对于 Controller层的代码测试,因为单元测试的局限性,所以我们需要采用集成测试,本文将使用SpringBootTest 和 Mockito两个主流的测试框架进行分析,下面先说明几个重要的组件:
@WebMvcTest是 Spring Boot提供的一个注解,用于测试 SpringMVC的 Controller,它能够启动一个最小化的 Spring应用上下文,包含仅与 Web层相关的 bean,从而快速且高效地测试 Controller的功能。
@WebMvcTest主要功能如下:
MockMvc是 Spring框架中用于测试 Spring MVC控制器的主要工具,它允许开发者在不启动整个 HTTP服务器的情况下,测试控制器的请求处理方法。MockMvc 提供了一种方式来模拟 HTTP请求并验证响应,确保控制器行为符合预期。
MockMvc的主要功能如下:
Mockito 是一个流行的 Java测试框架,用于创建和配置 mock 对象。它主要用于单元测试中,通过模拟依赖对象的行为,使得测试目标对象能够独立于其依赖项进行测试。
Mockito主要功能如下:
它是 Jackson库中的核心类之一,用于将 Java 对象转换为 JSON 字符串,或者将 JSON 字符串转换为 Java 对象。在 Spring 应用中,ObjectMapper 被广泛用于处理 JSON 数据。
ObjectMapper的主要功能如下:
所以,一个包含上述所有组件的完整代码示例如下:
@WebMvcTest(controllers = RegisterRestController.class)class RegisterRestControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private XXX xxx; // 用户业务的bean @Test void test() throws Exception { mockMvc.perform(...); }}
介绍了上面几个集成测试的组件之后,接下来我们就可以详解的分析他们是如何应用到 Controller的集成测试中。
我们按照 Controller的 6个主要职责来分别讲解他们是如何进行测试的。
Controller是接收请求的入口,因此对于 Controller接收 HTTP(s)请求验证 Controller是否侦听某个 HTTP 请求非常简单,我们只需调用 MockMvc.perform() 方法,返回 200代表成功,返回非200就代表异常,示例代码如下:
@Testvoid whenReceiveHttpRqe_thenReturns200() throws Exception { mockMvc.perform(post("/user/register") .contentType("application/json")) .andExpect(status().isOk()); }
Controller是通过@PathVariable、@RequestBody、@RequestParam 3种方式来接收参数的,因此,下面的示例代码分别模拟这 3种方式是如何进行参数传递的。
@Autowiredprivate ObjectMapper objectMapper;@Testvoid whenValidInput_thenReturns200() throws Exception { User user = new User("zhangsan", 21); mockMvc.perform(post("/user/register/{id}", 1111) // 模拟通过 @PathVariable传递参数 .contentType("application/json") .param("name", "张三") // 模拟通过 @RequestParam传递参数 .content(objectMapper.writeValueAsString(user))) // 模拟通过 @RequestBody传递参数 .andExpect(status().isOk());}
通过上面的方式,我们模拟了一个正常的 HTTP请求,并且使用 3种方式进行参数传递。
测试参数的校验,主要是为了检查代码逻辑有没有对参数进行有效的验证,比如,必填字段判空,字符串最大长度限制,数字最大值和最小值校验,手机号或邮箱格式校验等。
参数校验测试一般分正常测试和按预期失败的异常测试,特别需要进行边界的测试。如下示例,给出了测试通过和符合预期并返回 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); // 当 nickname为空时,会抛出 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());}
对于业务方法调用的测试,通常我们会使用Mockito.mock来模拟业务方法的返回值,而业务方式的真实运行逻辑会在 Server的单元测试中完成,如下示例代码:
// 调用注册的业务方法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")));}
在业务代码执行完之后,我们需要对 HTTP对应响应,因此可以使用 andReturn()方法将 HTTP交互的结果存储在 MvcResult类型的变量中,然后从响应正文中读取 JSON字符串,并使用 isEqualToIgnoringWhitespace()将其与预期字符串进行比较,如下示例:
// 调用注册的业务方法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(); //将结果返回 String expectedResponse = "userId"; String actualResponseBody = mvcResult.getResponse().getContentAsString(); assertThat(actualResponseBody).isEqualToIgnoringWhitespace( objectMapper.writeValueAsString(expectedResponse));}
测试异是指如果发生异常,Controller应返回特定的 HTTP状态,比如 200,400,500等等, 默认情况下,Spring 会处理其中的大多数情况。但是,如果我们有一个自定义异常处理,我们想要测试它。假设我们要返回一个结构化的 JSON 错误响应,其中包含请求中每个无效字段的字段名称和错误消息。我们会创建一个这样的@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);}
本文结合 SpringBootTest 和 Mockito两个主流的测试框架,对 Controller各个职责进行全面的测试,并且给出了比较详细的示例代码,通过本文的分析,我们不仅可以学会对 Controller的测试,同时还应该触类旁通,将里面优秀的思维应用到其他层级代码的测试。
代码质量是开发人员必须守住的底线,所以在日常的开发中一定要秉着对自己负责的态度,采用单元测试和集成测试对自己的代码进行测试。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-96427-0.html新来的腾讯后端,直接定义代码测试新姿势!
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com