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类中找到,该类是请求结果的匹配的一个工具类。

相关推荐
代码之光_198036 分钟前
保障性住房管理:SpringBoot技术优势分析
java·spring boot·后端
ajsbxi41 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
颜淡慕潇2 小时前
【K8S问题系列 |1 】Kubernetes 中 NodePort 类型的 Service 无法访问【已解决】
后端·云原生·容器·kubernetes·问题解决
戴眼镜的猴2 小时前
Spring Boot的过滤器与拦截器的区别
spring boot
尘浮生2 小时前
Java项目实战II基于Spring Boot的光影视频平台(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·maven·intellij-idea
尚学教辅学习资料3 小时前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
morris1313 小时前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
monkey_meng4 小时前
【Rust中的迭代器】
开发语言·后端·rust
余衫马4 小时前
Rust-Trait 特征编程
开发语言·后端·rust