Spring Boot单元测试入门实战

一、关于JUnit的一些东西

在我们开发Web应用时,经常会直接去观察结果进行测试。虽然也是一种方式,但是并不严谨。作为开发者编写测试代码来测试自己所写的业务逻辑是,以提高代码的质量、降低错误方法的概率以及进行性能测试等。经常作为开发这写的最多就是单元测试。引入spring-boot-starter-testSpringBoot的测试依赖。该依赖会引入JUnit的测试包,也是我们用的做多的单元测试包。而Spring Boot在此基础上做了很多增强,支持很多方面的测试,例如JPA,MongoDB,Spring MVC(REST)和Redis等。

接下来主要是测试业务逻辑层的代码,REST和Mock测试。

1.1 JUnit介绍

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。

JUnit相关概念 含义
测试 一个以@Test注释的方法定义一个测试,运行这个方法,JUnit会创建一个包含类的实例,然后再调用这个被注释的方法。
测试类 包含多个@Test方法的一个类
Assert 定义想测试的条件,当条件成立时,assert 方法保持沉默,条件不成立时,则抛出异常
Suite Suite允许将测试类归类成一组
Runner Runner类用于运行测试,JUnit4是向后兼容的,可以运行JUnit3的测试实例

这里使用的是JUnit4.x版本,JUnit中有两个重要的类Assume+Assert,以及重要的注解:BeforeClass、AfterClass、After、Before、Test和Ignore。BeforeClass和AfterClass在每个类的开始和结束的时候运行,需要static修饰方法。而Before和After则是在每个测试方法的开始和结束的时候运行。

代码片段:TestDeployApplication.class是自己编写的Spring Boot启动类。

复制代码
  1. @RunWith(SpringRunner.class)

  2. @SpringBootTest(classes = {TestDeployApplication.class})

  3. public class UnitTest1 {

  4. @BeforeClass

  5. public static void beforeClass() {

  6. System.out.println("=================BeforeClass================");

  7. }

  8. @AfterClass

  9. public static void afterClass() {

  10. System.out.println("=================AfterClass================");

  11. }

  12. @Before

  13. public void beforeTest() {

  14. System.out.println("before test");

  15. }

  16. @After

  17. public void afterTest() {

  18. System.out.println("after test");

  19. }

  20. @Test

  21. public void test1() {

  22. System.out.println("test1");

  23. }

  24. @Test

  25. public void test2() {

  26. System.out.println("test2");

  27. }

  28. }

1.2 JUnit的Assert类

Assert类中常用的方法:

  • assertEquals("提示信息",A,B):当判断A是否等于B,不等于就抛出错误。比较对象是调用的是equals()方法。
  • assertSame("提示信息",A,B):判断对象是否相同。
  • assertTrue("提示信息",A):判断条件A是否为真。
  • assertFalse("提示信息",A):判断条件是否为假。
  • assertNotNull("提示信息",A):判断对象是否不为空。
  • assertNull("提示信息",A):判断对象是否不为空。
  • assertArrayEqual("提示信息",A,B):判断数组A和数组B是否相等。
1.3 JUnit的Suite

JUnit的Suite设计就是一次性运行一个或多个测试用例,Suite可以看作是一个容器,用来把测试类归类在一起,并把他们作为一个集合来运行,运行器启动Suite。

复制代码
  1. @RunWith(Suite.class)

  2. @SuiteClasses({UnitTest1.class,UnitTest2.class})

  3. public class MainTest{

  4. }

二、Spring Boot单元测试

添加需要的依赖

复制代码
  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-test</artifactId>

  4. <scope>test</scope>

  5. </dependency>

2.1 Spring Boot测试依赖提供的测试范围

引入了spring-boot-starter-test继承了很多的测试库:

  • JUnit,标准的单元测试Java程序。
  • Spring Test和Spring Boot Test,对Spring Boot应用的单元测试。
  • Mockito,Java Mock测试框架,用于模拟任何Spring管理的Bean。例如在- - 单元测试中,模拟一个第三方系统接口返回的数据,而不用真正地去请求第三方接口。
  • AssertJ,一个assertion库,同时提供了更加多的期望值与测试返回值的比较方式。
  • Hamcrest,库的匹配对象。
  • JSONassert,对JSON对象或者JSON字符串断言的库。
  • JSONPath,提供向XPath那样的符号来获取JSON字段。

2.2 Spring Boot单元测试的脚手架

在使用spring.io创建的Spring Boot工程中,就默认常见了一个单元测试的类。

复制代码
  1. @RunWith(SpringRunner.class)

  2. @SpringBootTest

  3. public class UnitTest1 {

  4. @Test

  5. public void contextLoads(){

  6. }

  7. }

@RunWith是JUnit中的注解,用来通知JUnit单元测试框架不要使用内置的方式进行单元测试,向上面的写法,就是指定使用SpringRunner类来提供单元测试。

@SpringBootTest注解则是用于Spring Boot应用的测试,默认会分局报名逐级往上查找Spring Boot主程序,也就是@SpringBootApplocation注解,并在单元测试启动的时候启动该类来创建Spring上下文。所以我们在对Spring Boot应用进行单元测试的时候,在日志输出都可以看到Spring Boot应用的启动日志。

2.3 对Service层代码测试
复制代码
  1. import static org.mockito.BDDMockito.given;

  2. import static org.mockito.Mockito.*;

  3. @RunWith(SpringRunner.class)

  4. @SpringBootTest

  5. @Transactional

  6. public class ServiceUnitTest {

  7. @MockBean

  8. private ThirdSystemService thirdSystemService;

  9. @Autowired

  10. private ISysUserService userService;

  11. @Test

  12. public void test1() {

  13. Long expectResult = 100L;

  14. given(thirdSystemService.develop()).willReturn(expectResult);

  15. SysUser sysUser = userService.findById(expectResult);

  16. System.out.println(sysUser.toString());

  17. }

  18. }

@MockBean可以获取在Spring下上文管理的Bean,但是thirdSystemService这个Bean并不是真的实列,而是通过Mockito工具创建的测试实例。通过@MockBean注解模拟出来的Bean,调用方法是不会真正的调用真正的方法,适用于在依赖了第三方的系统,然而第三方的系统的对接并没有实现完成,自己可以单独测试自己的业务代码。willReturn(expectResult)说明结果永远返回100L。

2.5 测试MVC代码

Spring Boot中还能单独测试Controller的代码,例如测试Controller中方法的参数绑定和校验之类的逻辑。可以通过@WebMvcTest注解来完成单元测试。

复制代码
  1. @RunWith(SpringRunner.class)

  2. @WebMvcTest(SysUserController.class)

  3. public class ServiceUnitTest {

  4. @Autowired

  5. private MockMvc mockMvc;

  6. @Test

  7. public void test2() throws Exception {

  8. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/hello/{id}", 1L);

  9. mockMvc.perform(requestBuilder)

  10. .andExpect(MockMvcResultMatchers.status().isOk())

  11. .andDo(MockMvcResultHandlers.print());

  12. }

  13. }

像Get方法传递参数

复制代码
  1. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders

  2. .get("/hello/{id}", 1L) // path变量

  3. .param("name", "hello"); // @RequestParam 获取变量。post请求也适用

文件上传

复制代码
  1. @RunWith(SpringRunner.class)

  2. @WebMvcTest(SysUserController.class)

  3. public class ServiceUnitTest {

  4. @Autowired

  5. private MockMvc mockMvc;

  6. @Test

  7. public void test3() throws Exception {

  8. // 获取文件

  9. FileInputStream fileInputStream = new FileInputStream("文件路径");

  10. // 构建文件上传对象

  11. MockMultipartFile mockMultipartFile = new MockMultipartFile("file", fileInputStream);

  12. // 构建mock文件上传请求

  13. MockMultipartHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart("/upload").file(mockMultipartFile);

  14. // 发送请求

  15. mockMvc.perform(requestBuilder)

  16. .andExpect(MockMvcResultMatchers.status().isOk())

  17. .andDo(MockMvcResultHandlers.print());

  18. }

  19. }

复制代码
  1. 模拟Cookie和Session

  2. @RunWith(SpringRunner.class)

  3. @WebMvcTest(SysUserController.class)

  4. public class ServiceUnitTest {

  5. @Autowired

  6. private MockMvc mockMvc;

  7. @Test

  8. public void test4() throws Exception {

  9. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders

  10. .get("index.html")

  11. .sessionAttr("name", "hello")

  12. .cookie(new Cookie("token", "123345"));

  13. mockMvc.perform(requestBuilder)

  14. .andExpect(MockMvcResultMatchers.status().isOk())

  15. .andDo(MockMvcResultHandlers.print());

  16. }

  17. }

设置请求头

复制代码
  1. @RunWith(SpringRunner.class)

  2. @WebMvcTest(SysUserController.class)

  3. public class ServiceUnitTest {

  4. @Autowired

  5. private MockMvc mockMvc;

  6. @Test

  7. public void test5() throws Exception {

  8. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders

  9. .get("index.html")

  10. .content(MediaType.APPLICATION_JSON_VALUE) // 期望返回类型

  11. .contentType(MediaType.APPLICATION_JSON_VALUE) // 提交的内容类型

  12. .header("token", 1235); // 设置请求头

  13. mockMvc.perform(requestBuilder)

  14. .andExpect(MockMvcResultMatchers.status().isOk())

  15. .andDo(MockMvcResultHandlers.print());

  16. }

  17. }

2.6 比较返回结果

MockMvc类的perform方法会返回一个ResultAction类,可以对结果进行一些操作(andExpect、andDo和andReturn)。

复制代码
  1. @RunWith(SpringRunner.class)

  2. @WebMvcTest(SysUserController.class)

  3. public class ServiceUnitTest {

  4. @Autowired

  5. private MockMvc mockMvc;

  6. @Test

  7. public void test2() throws Exception {

  8. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders

  9. .get("/hello/{id}", 1L)

  10. .param("name", "hello");

  11. mockMvc.perform(requestBuilder)

  12. .andExpect(MockMvcResultMatchers.jsonPath("$.id", "id").value(2L));

  13. .andDo(MockMvcResultHandlers.print());

  14. }

  15. }

例如上面获取返回的JSON结果中的id字段的值,value是期望值,如果期望值与实际值不一样测试就会报错。

也可以断言测试返回结果的View(视图)和Model(数据模型)是否是期望值

复制代码
  1. @RunWith(SpringRunner.class)

  2. @WebMvcTest(SysUserController.class)

  3. public class ServiceUnitTest {

  4. @Autowired

  5. private MockMvc mockMvc;

  6. MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders

  7. .get("/hello/{id}", 1L)

  8. .param("name", "hello");

  9. mockMvc.perform(requestBuilder)

  10. // 断言返回的试图

  11. .andExpect(MockMvcResultMatchers.view().name("index.html"))

  12. // 断言返回的数据模型中的数据

  13. .andExpect(MockMvcResultMatchers.model().attribute("id",1L))

  14. .andDo(MockMvcResultHandlers.print());

  15. }

更多的结果断言可以在MockMvcResultMatchers类中找到,该类是请求结果的匹配的一个工具类。

相关推荐
元闰子21 小时前
OLTP上云,哪种架构最划算?·VLDB'25
数据库·后端·云原生
IT_陈寒21 小时前
Vite 5.0重磅升级:8个性能优化秘诀让你的构建速度飙升200%!🚀
前端·人工智能·后端
hui函数21 小时前
scrapy框架-day02
后端·爬虫·python·scrapy
Moshow郑锴21 小时前
SpringBootCodeGenerator使用JSqlParser解析DDL CREATE SQL 语句
spring boot·后端·sql
小沈同学呀1 天前
创建一个Spring Boot Starter风格的Basic认证SDK
java·spring boot·后端
方圆想当图灵1 天前
如何让百万 QPS 下的服务更高效?
分布式·后端
凤山老林1 天前
SpringBoot 轻量级一站式日志可视化与JVM监控
jvm·spring boot·后端
凡梦千华1 天前
Django时区感知
后端·python·django
Chan161 天前
JVM从入门到实战:从字节码组成、类生命周期到双亲委派及打破双亲委派机制
java·jvm·spring boot·后端·intellij-idea