Skip to content

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注释已经用它注释。
  • 使用 @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 =============