1、SpringBoot单元测试
单元测试(Unit Test)是为了检验程序的正确性。一个单元可能是单个程序、类、对象、方法等,它是应用程序的最小可测试部件。SpringBoot提供了 spring-boot-starter-test 启动器。通过它,能引入一些有用的测试库。
1.1 快速创建单元测试
(1)在SpringBoot中进行单元测试很简单,它已经自动添加好了Test的Starter依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
(2)只要在“src/test/java”目录下新建一个测试类即可,代码如下:
import com.pjb.entity.UserInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 用户业务逻辑测试类(JUnit4)
* @author pan_junbiao
**/
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest
{
@Autowired
private UserService userService;
@Before
public void setUp()
{
}
@After
public void tearDown()
{
}
@Test
public void getUserInfoById()
{
//测试代码
UserInfo userInfo = userService.getUserInfoById(1);
}
}
代码说明:
@SpringBootTest:是SpringBoot用于测试的注解,可指定入口类或测试环境等。
@RunWith(SpringRunner.class):让测试运行与Spring的测试环境。
@Test:表示为一个测试单元。
1.2 测试的回滚
在单元测试中可能会产生垃圾数据,可以开启事务功能进行回滚——在方法或者类头部添加@Transactional注解即可,代码如下:
/**
* 新增员工
* 加上@Transactional注解,开启事务
* 这样测试执行完后就会进行回滚操作
* @author pan_junbiao
*/
@Test
@Transactional
public void addStaff()
{
//创建新用户信息
UserInfo userInfo = new UserInfo();
userInfo.setUserName("pan_junbiao的博客");
userInfo.setBlogUrl("https://blog.csdn.net/pan_junbiao");
userInfo.setBlogRemark("您好,欢迎访问 pan_junbiao的博客");
//执行新增操作
userDao.save(userInfo);
}
2、JUnit5测试框架
JUnit5是对程序代码进行单元测试的Java框架。它用来编写自动化测试工具,降低测试的难度、减少烦琐性、并有效避免出现程序错误。
2.1 JUnit5注解
所有支持的注解都在包 org.junit.jupiter.api 下;
使用@Test、@TestTemplate、@RepeatedTest、@BeforeAll、@AfterAll、@BeforeEach或@AfterEach注释的方法不能返回值。
注解 | 说明 |
---|---|
@Test | 表示方法是测试方法。与JUnit 4的@Test注释不同,这个注释不声明任何属性,因为JUnit Jupiter中的测试扩展基于它们自己的专用注释进行操作。 |
@ParameterizedTest | 表示方法是参数化测试。 |
@RepeatedTest | 表示方法是重复测试的测试模板。 |
@TestFactory | 表示方法是动态测试的测试工厂。 |
@TestInstance | 用于为带注释的测试类配置测试实例生命周期。 |
@TestTemplate | 表示方法是为测试用例设计的模板,根据注册提供程序返回的调用上下文的数量进行多次调用。 |
@DisplayName | 声明测试类或测试方法的自定义显示名称。 |
@BeforeEach | 表示在当前类中每个@Test、@RepeatedTest、@ParameterizedTest或@TestFactory方法之前执行注释的方法;类似于JUnit 4的@Before。 |
@AfterEach | 表示在当前类中的每个@Test、@RepeatedTest、@ParameterizedTest或@TestFactory方法之后,都应该执行带注释的方法;类似于JUnit 4的@After。 |
@BeforeAll | 表示应在当前类中的所有@Test、@RepeatedTest、@ParameterizedTest和@TestFactory方法之前执行带注释的方法;类似于JUnit 4的@BeforeClass。 |
@AfterAll | 表示在当前类中,所有@Test、@RepeatedTest、@ParameterizedTest和@TestFactory方法都应该执行注释的方法;类似于JUnit 4的@AfterClass。 |
@Nested | 表示带注释的类是一个嵌套的、非静态的测试类。@BeforeAll和@AfterAll方法不能直接在 @Nested 测试类中使用,除非使用“每个类”测试实例生命周期。 |
@Tag | 用于在类或方法级别声明过滤测试的标记;类似于TestNG中的测试组或JUnit 4中的类别。 |
@Disabled | 用于禁用测试类或测试方法;类似于JUnit 4的@Ignore。 |
@ExtendWith | 用于注册自定义扩展。 |
2.2 JUnit5断言(Assertions类)
JUnit5断言都是 org.junit.jupiter.api.Assertions 中的静态方法断言类。
Asser类中主要方法如下:
方法名称 | 方法描述 |
---|---|
assertEquals | 断言传入的预期值与实际值是相等的。 |
assertNotEquals | 断言传入的预期值与实际值是不相等的。 |
assertArayEquals | 断言传入的预期数组与实际数组是相等的。 |
assertNull | 断言传入的对象是为空。 |
assertNotNull | 断言传入的对象是不为空。 |
assertTrue | 断言条件为真。 |
assertFalse | 断言条件为假。 |
assertSame | 断言两个对象引用同一个对象,相当于"==”。 |
assertNotSame | 断言两个对象引用不同的对象,相当于"!=”。 |
assertThat | 断言实际值是否满足指定的条件。 |
【示例】SpringBoot 基于 JUnit5 的测试类。
package com.pjb;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* 用户业务逻辑测试类(JUnit5)
* @author pan_junbiao
**/
@SpringBootTest
public class UserServiceTest
{
@BeforeEach
void setUp()
{
}
@AfterEach
void tearDown()
{
}
@Autowired
private UserService userService;
@Test
public void getUserInfoById()
{
//测试代码
UserInfo userInfo = userService.getUserInfoById(1);
}
}
3、项目的单元测试
创建SpringBoot项目,实现Controller层、Service层、Dao层的单元测试。
3.1 Controller层的单元测试
Mockito是GitHub上使用最广泛的Mocking框架。它提供简洁的API来测试。Mockito简单易学、可读性强、验证语法简洁。
【示例】使用Mockito框架,对Controller层进行单元测试。
(1)创建UserController(用户信息控制器)。
package com.pjb.controller;
import com.pjb.entity.UserInfo;
import com.pjb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 用户信息控制器
* @author pan_junbiao
**/
@RestController
@RequestMapping("user")
public class UserController
{
@Autowired
private UserService userService;
/**
* 获取用户信息
* RESTful接口
*/
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public UserInfo getUserInfo(@PathVariable("id")int userId)
{
UserInfo userInfo = userService.getUserInfoById(userId);
return userInfo;
}
/**
* 根据用户ID,获取用户信息
*/
@RequestMapping("/getUserInfoById")
public UserInfo getUserInfoById(@RequestParam(value = "user_id", defaultValue = "0") int userId)
{
UserInfo userInfo = userService.getUserInfoById(userId);
return userInfo;
}
/**
* 新增用户信息
* 参数:接收对象型参数
*/
@RequestMapping(value = "/addUserByEntity", method = RequestMethod.POST)
public boolean addUserByEntity(@RequestBody UserInfo userInfo)
{
boolean result = userService.addUser(userInfo);
return result;
}
/**
* 新增用户信息
* 参数:接收多个参数
*/
@RequestMapping(value = "/addUserByParam", method = RequestMethod.POST)
public boolean addUserByParam(String userName,String blogUrl,String blogRemark)
{
UserInfo userInfo = new UserInfo();
userInfo.setUserName(userName);
userInfo.setBlogUrl(blogUrl);
userInfo.setBlogRemark(blogRemark);
boolean result = userService.addUser(userInfo);
return result;
}
}
(2)实现Controller层的单元测试。
package com.pjb.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pjb.entity.UserInfo;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
/**
* 用户信息控制器测试类
* @author pan_junbiao
**/
@SpringBootTest
@RunWith(SpringRunner.class)
class UserControllerTest
{
//启用Web上下文
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@BeforeEach
private void setUp()
{
//使用上下文构建MockMvc
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@AfterEach
private void tearDown()
{
}
/**
* 根据用户ID,获取用户信息
* RESTful接口
*/
@Test
public void getUserInfo() throws Exception
{
//执行请求(使用GET请求,RESTful接口)
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/{id}",1).accept(MediaType.APPLICATION_JSON_UTF8)).andReturn();
//获取返回编码
int status = mvcResult.getResponse().getStatus();
//获取返回结果
String content = mvcResult.getResponse().getContentAsString();
//断言,判断返回编码是否正确
Assert.assertEquals(200,status);
//将JSON转换为对象
ObjectMapper mapper = new ObjectMapper();
UserInfo userInfo = mapper.readValue(content, UserInfo.class);
//打印结果
System.out.println("用户ID:" + userInfo.getUserId());
System.out.println("用户姓名:" + userInfo.getUserName());
System.out.println("博客地址:" + userInfo.getBlogUrl());
System.out.println("博客信息:" + userInfo.getBlogRemark());
}
/**
* 根据用户ID,获取用户信息
* 使用GET请求
*/
@Test
public void getUserInfoById() throws Exception
{
//执行请求(使用GET请求)
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/getUserInfoById").param("user_id","1").accept(MediaType.APPLICATION_JSON_UTF8)).andReturn();
//获取返回编码
int status = mvcResult.getResponse().getStatus();
//获取返回结果
String content = mvcResult.getResponse().getContentAsString();
//断言,判断返回编码是否正确
Assert.assertEquals(200,status);
//将JSON转换为对象
ObjectMapper mapper = new ObjectMapper();
UserInfo userInfo = mapper.readValue(content, UserInfo.class);
//打印结果
System.out.println("用户ID:" + userInfo.getUserId());
System.out.println("用户姓名:" + userInfo.getUserName());
System.out.println("博客地址:" + userInfo.getBlogUrl());
System.out.println("博客信息:" + userInfo.getBlogRemark());
}
/**
* 新增用户信息
* 使用POST请求,传递对象型参数
*/
@Test
public void addUserByEntity() throws Exception
{
//创建新用户
UserInfo userParam = new UserInfo();
userParam.setUserName("pan_junbiao的博客");
userParam.setBlogUrl("https://blog.csdn.net/pan_junbiao");
userParam.setBlogRemark("您好,欢迎访问 pan_junbiao的博客");
//将参数转换成JSON对象
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(userParam);
//执行请求(使用POST请求,传递对象参数)
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/addUserByEntity").content(json).contentType(MediaType.APPLICATION_JSON)).andReturn();
//获取返回编码
int status = mvcResult.getResponse().getStatus();
//获取返回结果
String content = mvcResult.getResponse().getContentAsString();
//断言,判断返回编码是否正确
Assert.assertEquals(200,status);
//断言,判断返回结果是否正确
Assert.assertEquals("true",content);
}
/**
* 多个参数的传递
* 使用POST请求,传递多个参数
*/
@Test
public void addUserByParam() throws Exception
{
//多个参数的传递
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("userName","pan_junbiao的博客");
params.add("blogUrl","https://blog.csdn.net/pan_junbiao");
params.add("blogRemark","您好,欢迎访问 pan_junbiao的博客");
//执行请求(使用POST请求,传递多个参数)
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/addUserByParam").params(params)).andReturn();
//获取返回编码
int status = mvcResult.getResponse().getStatus();
//获取返回结果
String content = mvcResult.getResponse().getContentAsString();
//断言,判断返回编码是否正确
Assert.assertEquals(200,status);
//断言,判断返回结果是否正确
Assert.assertEquals("true",content);
}
}
执行结果:
3.2 Service层的单元测试
【示例】Service层进行单元测试,使用Assert方法进行断言结果。
package com.pjb.service;
import com.pjb.entity.UserInfo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* 用户业务逻辑测试类
* @author pan_junbiao
**/
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserServiceTest
{
@Autowired
private UserService userService;
@Test
public void getUserInfoById()
{
//获取用户信息
UserInfo userInfo = userService.getUserInfoById(1);
//断言
Assert.assertEquals(1,userInfo.getUserId());
Assert.assertEquals("pan_junbiao的博客",userInfo.getUserName());
Assert.assertEquals("https://blog.csdn.net/pan_junbiao",userInfo.getBlogUrl());
Assert.assertEquals("您好,欢迎访问 pan_junbiao的博客",userInfo.getBlogRemark());
}
}
3.3 Dao层的单元测试
【示例】Dao层进行单元测试,以及使用@Transactional注解进行回滚操作。
import com.pjb.entity.UserInfo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户信息数据库访问测试类
* @author pan_junbiao
**/
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserDaoTest
{
@Autowired
private UserDao userDao;
/**
* 新增员工
* 加上@Transactional注解,开启事务
* 这样测试执行完后就会进行回滚操作
* @author pan_junbiao
*/
@Test
@Transactional
public void addStaff()
{
//创建新用户信息
UserInfo userInfo = new UserInfo();
userInfo.setUserName("pan_junbiao的博客");
userInfo.setBlogUrl("https://blog.csdn.net/pan_junbiao");
userInfo.setBlogRemark("您好,欢迎访问 pan_junbiao的博客");
//执行新增操作
userDao.save(userInfo);
}
}