Junit5
是当前一代的JUnit测试框架
,它为 JVM 上的开发人员端测试提供了现代基础。 这包括专注于 Java8 和更高版本,以及支持许多不同样式的测试。
一、Spring Boot Testing
官方: https://docs.spring.io/spring-boot/docs/2.6.13/reference/html/features.html#features.testing
SpringBoot 提供了许多实用工具和注释来帮助测试应用程序。 测试支持由两个模块提供:
spring-boot-test
包含核心项目spring-boot-test-autoconfigure
支持自动配置测试。
大多数开发人员使用spring-boot-starter-test
“Starter”
- 它导入
SpringBoot测试模块
以及JUnitJupiter、AssertJ、Hamcrest和其他
一些有用的库。
1. 添加依赖
- 在
pom.xml
添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<scope>test</scope>
只用于测试环境
或开发环境
,不允许在生产环境
进行测试用例运行。
这些 spring-boot-starter-test
`(在 test scope)包含以下提供的库:
JUnit 5
: 用于单元测试 Java 应用程序的事实标准。Spring Test & Spring Boot Test
: 为 Spring Boot 应用程序提供实用程序和集成测试支持。AssertJ:
一个流畅的断言库。Hamcrest
: 匹配器对象库(也称为约束或谓词)。Mockito
: Java 模拟框架。JSONassert
: JSON 的断言库。JsonPath
: JSON 的 XPath。
我们通常发现这些公共库在编写测试时很有用。 如果这些库不适合您的需要,您可以添加自己的其他测试依赖项。
2. 编写测试类
提示
- 如果您正在使用
JUnit 4
,请不要忘记还添加@RunWith(SpringRunner.class)
否则注释将被忽略。 - 如果您使用的是
JUnit 5
,则无需添加@ExtendWith(SpringExtension.class)
作为@SpringBootTest
而另一个@…Test
注释已经用它注释。
- 通过
IDEA
开发工具,以第二章项目为例,实现单元测试。
- 使用
@SpringBootTest
注解
java
package com.calvin.spring.boot.example.mybatis.plus.service;
import com.calvin.spring.boot.example.mybatis.plus.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
* 用户接口类-单元测试
*
* @author Calvin
* @date 2024/1/25
* @since v1.0.0
*/
@Slf4j
@SpringBootTest
public class IUserServiceTest {
@Resource
IUserService iUserService;
@Test
void searchByName() {
Date startDate = new Date();
this.runTimeLog("按名称搜索方法", true, false, null, null, false, null);
List<User> users = iUserService.searchByName("Calvin");
this.runTimeLog("按名称搜索方法", true, true, startDate, new Date(), true, users.isEmpty() ? null : users.toString() );
}
public void runTimeLog(String methodName, boolean start, boolean isEndTime, Date startDate, Date endDate, boolean isPrintResult, String jsonResult) {
if (isEndTime) {
double seconds = (endDate.getTime() - startDate.getTime()) / 1000d;
log.info("【{}】耗时: {} 秒",methodName, seconds);
}
if (isPrintResult) {
log.info("【{}】执行结果:{}",methodName, jsonResult);
}
log.info("============= 用户接口类-{}-单元测试: {} =============", methodName, start ? "BEGIN" : "END");
}
}
- 输出结果:
log
2024-01-25 17:10:41.879 INFO 27487 --- [ main] c.c.s.b.e.m.p.service.IUserServiceTest : ============= 用户接口类-按名称搜索方法-单元测试: BEGIN =============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@345d053b] was not registered for synchronization because synchronization is not active
2024-01-25 17:10:41.908 INFO 27487 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-01-25 17:10:42.178 INFO 27487 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@528965756 wrapping com.mysql.cj.jdbc.ConnectionImpl@4b862408] will not be managed by Spring
==> Preparing: SELECT * FROM user WHERE name like CONCAT('%',?,'%')
==> Parameters: Calvin(String)
<== Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@345d053b]
2024-01-25 17:10:42.226 INFO 27487 --- [ main] c.c.s.b.e.m.p.service.IUserServiceTest : 【按名称搜索方法】耗时: 0.347 秒
2024-01-25 17:10:42.226 INFO 27487 --- [ main] c.c.s.b.e.m.p.service.IUserServiceTest : 【按名称搜索方法】执行结果:null
2024-01-25 17:10:42.226 INFO 27487 --- [ main] c.c.s.b.e.m.p.service.IUserServiceTest : ============= 用户接口类-按名称搜索方法-单元测试: BEGIN =============
Controller 控制器类
进行单元测试: 需要启动完全运行的服务器,我们建议您使用随机端口
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
: 每次运行测试时,都会随机选择可用的端口。@TestPropertySource
: 用于在 Spring Boot 测试中加载额外的属性文件。它可以应用于类或方法,以加载指定的属性文件@LocalServerPort
: 注解被应用于字段 port,以获取随机端口号TestRestTemplate
: 通过 TestRest 模板进行请求接口。
java
package com.calvin.spring.boot.example.mybatis.plus.controller;
import com.calvin.spring.boot.example.mybatis.plus.entity.User;
import com.calvin.spring.boot.example.mybatis.plus.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource;
import javax.annotation.Resource;
import java.util.Date;
/**
* @author Calvin
* @date 2024/1/25
* @since v1.0.0
*/
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // SpringBootTest.WebEnvironment.RANDOM_PORT 随机端口
@TestPropertySource(properties = "server.port=0") // 用于在 Spring Boot 测试中加载额外的属性文件。它可以应用于类或方法,以加载指定的属性文件
public class UserControllerTest {
@Autowired
TestRestTemplate restTemplate;
@LocalServerPort // 注解被应用于字段 port,以获取随机端口号
private int port;
@Resource
IUserService iUserService;
@Test
void create() {
Date startDate = new Date();
User user = new User();
user.setName("BilBil");
user.setAge(20);
user.setEmail("bilbil@512.com");
String url = String.format("http://localhost:%s/user/create", port);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<User> requestEntity = new HttpEntity<>(user, headers);
this.apiResponseTimeLog("创建", true, false, startDate, null, false, null, false, null);
ResponseEntity<String> postResponse = restTemplate.postForEntity(url, requestEntity, String.class);
this.apiResponseTimeLog("创建", false, true, startDate, new Date(), true,beanToJson(user), true, postResponse.getBody());
}
@Test
void edit() {
Date startDate = new Date();
User user = iUserService.getById(1);
user.setName("admin");
String url = String.format("http://localhost:%s/user/edit", port);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<User> requestEntity = new HttpEntity<>(user, headers);
this.apiResponseTimeLog("编辑", true, false, startDate, null, false, null, false, null);
ResponseEntity<String> putResponse = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, String.class);
this.apiResponseTimeLog("编辑", false, true, startDate, new Date(), true,beanToJson(user), true, putResponse.getBody());
}
@Test
void find() {
Date startDate = new Date();
User req = new User();
req.setId(1L);
String url = String.format("http://localhost:%s/user/find?id=%s", port, req.getId());
this.apiResponseTimeLog("查询", true, false, startDate, null, false, null, false, null);
ResponseEntity<String> getResponse = restTemplate.getForEntity(url, String.class);
this.apiResponseTimeLog("查询", false, true, startDate, new Date(), true, beanToJson(req), true, getResponse.getBody());
}
@Test
void delete() {
Date startDate = new Date();
User req = new User();
req.setId(6L);
String url = String.format("http://localhost:%s/user/delete?id=%", port, req.getId());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
this.apiResponseTimeLog("删除", true, false, startDate, null, false, null, false, null);
ResponseEntity<String> getResponse = restTemplate.exchange(url, HttpMethod.DELETE, requestEntity, String.class);
this.apiResponseTimeLog("删除", false, true, startDate, new Date(), true, beanToJson(req), true, getResponse.getBody());
}
public static String beanToJson(Object bean) {
BeanMap beanMap = BeanMap.create(bean);
JSONObject jsonObject = new JSONObject();
beanMap.forEach((key, value) -> {
try {
jsonObject.putOpt((String) key, value);
} catch (JSONException e) {
throw new RuntimeException(e);
}
});
return jsonObject.toString();
}
public void apiResponseTimeLog(String methodName,
boolean start,
boolean isEndTime,
Date startDate,
Date endDate,
boolean isPrintReqParam,
String reqJsonParam,
boolean isPrintResult,
String jsonResult) {
if (isPrintReqParam) {
log.info("【{}】请求参数: {} ", methodName, reqJsonParam);
}
if (isEndTime) {
double seconds = (endDate.getTime() - startDate.getTime()) / 1000d;
log.info("【{}】耗时: {} 秒",methodName, seconds);
}
if (isPrintResult) {
log.info("【{}】执行结果:{}",methodName, jsonResult);
}
log.info("============= 用户API接口-{}-单元测试: {} =============", methodName, start ? "BEGIN" : "END");
}
}
- 输出结果
log
2024-01-25 17:13:49.326 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-创建-单元测试: BEGIN =============
2024-01-25 17:13:49.422 INFO 27684 --- [o-auto-1-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-01-25 17:13:49.422 INFO 27684 --- [o-auto-1-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-01-25 17:13:49.423 INFO 27684 --- [o-auto-1-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@22b7af2d] was not registered for synchronization because synchronization is not active
2024-01-25 17:13:49.512 INFO 27684 --- [o-auto-1-exec-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-01-25 17:13:49.744 INFO 27684 --- [o-auto-1-exec-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@660245368 wrapping com.mysql.cj.jdbc.ConnectionImpl@5cb05718] will not be managed by Spring
==> Preparing: INSERT INTO `user` ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: BilBil(String), 20(Integer), bilbil@512.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@22b7af2d]
2024-01-25 17:13:49.792 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【创建】请求参数: {"name":"BilBil","age":20,"email":"bilbil@512.com"}
2024-01-25 17:13:49.792 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【创建】耗时: 0.461 秒
2024-01-25 17:13:49.792 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【创建】执行结果:true
2024-01-25 17:13:49.792 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-创建-单元测试: END =============
2024-01-25 17:13:49.800 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-删除-单元测试: BEGIN =============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@24a30521] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@921456071 wrapping com.mysql.cj.jdbc.ConnectionImpl@5cb05718] will not be managed by Spring
==> Preparing: DELETE FROM `user` WHERE id=?
==> Parameters: 6(Long)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@24a30521]
2024-01-25 17:13:49.809 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【删除】请求参数: {"id":6}
2024-01-25 17:13:49.810 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【删除】耗时: 0.009 秒
2024-01-25 17:13:49.810 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【删除】执行结果:false
2024-01-25 17:13:49.810 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-删除-单元测试: END =============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@208205ed] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1886186662 wrapping com.mysql.cj.jdbc.ConnectionImpl@5cb05718] will not be managed by Spring
==> Preparing: SELECT id,name,age FROM `user` WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, age
<== Row: 1, admin, 18
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@208205ed]
2024-01-25 17:13:49.829 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-编辑-单元测试: BEGIN =============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f4bae05] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@211751146 wrapping com.mysql.cj.jdbc.ConnectionImpl@5cb05718] will not be managed by Spring
==> Preparing: UPDATE `user` SET name=?, age=? WHERE id=?
==> Parameters: admin(String), 18(Integer), 1(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f4bae05]
2024-01-25 17:13:49.838 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【编辑】请求参数: {"name":"admin","id":1,"age":18}
2024-01-25 17:13:49.838 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【编辑】耗时: 0.025 秒
2024-01-25 17:13:49.838 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【编辑】执行结果:true
2024-01-25 17:13:49.838 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-编辑-单元测试: END =============
2024-01-25 17:13:49.841 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-查询-单元测试: BEGIN =============
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@529eef04] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1549343527 wrapping com.mysql.cj.jdbc.ConnectionImpl@5cb05718] will not be managed by Spring
==> Preparing: SELECT id,name,age FROM `user` WHERE id=?
==> Parameters: 1(Long)
<== Columns: id, name, age
<== Row: 1, admin, 18
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@529eef04]
2024-01-25 17:13:49.846 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【查询】请求参数: {"id":1}
2024-01-25 17:13:49.846 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【查询】耗时: 0.005 秒
2024-01-25 17:13:49.846 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : 【查询】执行结果:{"id":1,"name":"admin","age":18,"email":null,"other":null,"idCard":null}
2024-01-25 17:13:49.846 INFO 27684 --- [ main] c.c.s.b.e.m.p.c.UserControllerTest : ============= 用户API接口-查询-单元测试: END =============