Spring Boot - 用JUnit 5构建完美的Spring Boot测试套件

文章目录

  • Pre
  • [JUnit 4 vs JUnit 5](#JUnit 4 vs JUnit 5)
  • [Junit5 常用注解](#Junit5 常用注解)
  • 栗子

Pre

SpringBoot - 单元测试利器Mockito入门

SpringBoot - 应用程序测试方案

SpringBoot - @SpringBootTest加速单元测试的小窍门

Spring Boot - Junit4 / Junit5 / Spring Boot / IDEA 关系梳理


java 复制代码
package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.STABLE;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;
import org.junit.platform.commons.annotation.Testable;

/**
 * {@code @Test} is used to signal that the annotated method is a
 * <em>test</em> method.
 *
 * <p>{@code @Test} methods must not be {@code private} or {@code static}
 * and must not return a value.
 *
 * <p>{@code @Test} methods may optionally declare parameters to be
 * resolved by {@link org.junit.jupiter.api.extension.ParameterResolver
 * ParameterResolvers}.
 *
 * <p>{@code @Test} may also be used as a meta-annotation in order to create
 * a custom <em>composed annotation</em> that inherits the semantics of
 * {@code @Test}.
 *
 * <h2>Test Execution Order</h2>
 *
 * <p>By default, test methods will be ordered using an algorithm that is
 * deterministic but intentionally nonobvious. This ensures that subsequent runs
 * of a test suite execute test methods in the same order, thereby allowing for
 * repeatable builds. In this context, a <em>test method</em> is any instance
 * method that is directly annotated or meta-annotated with {@code @Test},
 * {@code @RepeatedTest}, {@code @ParameterizedTest}, {@code @TestFactory}, or
 * {@code @TestTemplate}.
 *
 * <p>Although true <em>unit tests</em> typically should not rely on the order
 * in which they are executed, there are times when it is necessary to enforce
 * a specific test method execution order &mdash; for example, when writing
 * <em>integration tests</em> or <em>functional tests</em> where the sequence of
 * the tests is important, especially in conjunction with
 * {@link TestInstance @TestInstance(Lifecycle.PER_CLASS)}.
 *
 * <p>To control the order in which test methods are executed, annotate your
 * test class or test interface with {@link TestMethodOrder @TestMethodOrder}
 * and specify the desired {@link MethodOrderer} implementation.
 *
 * @since 5.0
 * @see RepeatedTest
 * @see org.junit.jupiter.params.ParameterizedTest
 * @see TestTemplate
 * @see TestFactory
 * @see TestInfo
 * @see DisplayName
 * @see Tag
 * @see BeforeAll
 * @see AfterAll
 * @see BeforeEach
 * @see AfterEach
 */
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@Testable
public @interface Test {
}

JUnit 4 vs JUnit 5

以下是JUnit 4和JUnit 5注解之间的一些主要区别

功能/特性 JUnit 4注解 JUnit 5注解
测试方法声明 @Test @Test
测试类声明 @RunWith @ExtendWith
断言 org.junit.Assert org.junit.jupiter.api.Assertions
测试生命周期 @BeforeAll, @BeforeEach, @AfterEach, @AfterAll
参数化测试 @Parameterized @ParameterizedTest
条件测试 @EnabledOnOs, @DisabledOnOs, @EnabledOnJre, @DisabledOnJre, 等等
标记重复测试 @RepeatedTest
依赖性测试 @Test中使用@TestDependsOn@TestDependency
测试实例生命周期 @TestInstance
测试接口和默认方法 不支持 支持
扩展模型 自定义Runner 自定义Extension

这些是JUnit 4和JUnit 5之间的一些重要区别,JUnit 5引入了许多新的功能和改进,以提供更灵活、强大的测试框架。你可以根据项目的需要选择适合的JUnit版本。


Junit5 常用注解

  • @SpringBootTest: 用于指定测试类启用Spring Boot Test,默认会提供Mock环境。
  • @ExtendWith: 如果只想启用Spring环境进行简单测试,不想启用Spring Boot环境,可以配置扩展为:SpringExtension
  • @Test: 指定方法为测试方法。
  • @TestMethodOrder: 用于配置测试类中方法的执行顺序策略,配置为OrderAnnotation时,按@Order顺序执行。
  • @Order: 用于配置方法的执行顺序,数字越低执行顺序越高。
  • @DisplayName: 用于指定测试类和测试方法的别名。
  • @BeforeAll: 在测试类的所有测试方法前执行一次,可用于全局初始化。
  • @AfterAll: 在测试类的所有测试方法后执行一次,可用于全局销毁资源。
  • @BeforeEach: 在测试类的每个测试方法前都执行一次。
  • @AfterEach: 在测试类的每个测试方法后都执行一次。
  • @Disabled: 禁用测试方法。
  • @RepeatedTest: 指定测试方法重复执行。
  • @ParameterizedTest: 指定参数化测试方法,类似重复执行,从@ValueSource中获取参数。
  • @ValueSource: 用于参数化测试指定参数。
  • @AutoConfigureMockMvc: 启用MockMvc的自动配置,可用于测试接口。

栗子

以下是上述注解的使用方法示例以及相应的Java代码:

  1. @SpringBootTest:用于指定Spring Boot测试。示例:
java 复制代码
@SpringBootTest
public class MySpringBootTest {
    // 测试方法
}
  1. @ExtendWith:用于配置测试类的执行环境。示例:
java 复制代码
@ExtendWith(SpringExtension.class)
public class MySpringTest {
    // 测试方法
}
  1. @Test:指定方法为测试方法。示例:
java 复制代码
@Test
public void testSomeMethod() {
    // 测试逻辑
}
  1. @TestMethodOrder@Order:配置测试方法的执行顺序。示例:
java 复制代码
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OrderedTestExample {

    @Order(1)
    @Test
    public void testMethod1() {
        // 测试逻辑
    }

    @Order(2)
    @Test
    public void testMethod2() {
        // 测试逻辑
    }
}
  1. @DisplayName:用于指定测试类和测试方法的别名。示例:
java 复制代码
@DisplayName("My Test Suite")
public class MyTestSuite {

    @Test
    @DisplayName("My Test Case")
    public void myTestCase() {
        // 测试逻辑
    }
}
  1. @BeforeAll@AfterAll:在测试类的所有测试方法前和后执行一次,可用于全局初始化和销毁资源。示例:
java 复制代码
@BeforeAll
public static void setup() {
    // 初始化操作
}

@AfterAll
public static void teardown() {
    // 资源销毁操作
}
  1. @BeforeEach@AfterEach:在测试类的每个测试方法前和后都执行一次。示例:
java 复制代码
@BeforeEach
public void beforeEachTest() {
    // 执行前的准备工作
}

@AfterEach
public void afterEachTest() {
    // 执行后的清理工作
}
  1. @Disabled:禁用测试方法。示例:
java 复制代码
@Test
@Disabled("This test is not ready yet.")
public void disabledTest() {
    // 未完成的测试逻辑
}
  1. @RepeatedTest:指定测试方法重复执行。示例:
java 复制代码
@RepeatedTest(5)
public void repeatedTest() {
    // 该测试方法会重复执行5次
}
  1. @ParameterizedTest@ValueSource:用于参数化测试。示例:
java 复制代码
@ParameterizedTest
@ValueSource(strings = { "apple", "banana", "cherry" })
public void testFruit(String fruit) {
    // 使用参数化的水果名称进行测试
}
  1. @AutoConfigureMockMvc:启用MockMvc的自动配置,可用于测试接口。示例:
java 复制代码
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testController() throws Exception {
        mockMvc.perform(get("/api/someendpoint"))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON));
    }
}

这些示例演示了如何使用这些注解来编写JUnit 5和Spring Boot测试。您可以根据您的具体需求和测试场景进行相应的配置和使用。


java 复制代码
package com.artisan.boottest.example;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BasicTest {

    @Test
    public void test() {
        String artisan = "artisan good";
        Assertions.assertEquals("artisan good", artisan);
    }
}
java 复制代码
package com.artisan.boottest.example;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
 * @author 小工匠
 * @version 1.0
 * @description: JUnit指定方法测试顺序 
 * @mark: show me the code , change the world
 */
@Slf4j
@ExtendWith(SpringExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class MethodOrderTest {


    @Test
    @Order(1)
    @DisplayName("order为1的方法")
    void lowOrder(){
        log.info("lowOrder method");
    }

    @Test
    @Order(2)
    @DisplayName("order为2的方法")
    void highOrder(){
        log.info("highOrder method");
    }
}
    
java 复制代码
package com.artisan.boottest.example;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
 * @author 小工匠
 * @version 1.0
 * @description: JUnit生命周期测试
 * @mark: show me the code , change the world
 */
@Slf4j
@ExtendWith(SpringExtension.class)
public class LifecycleTest {

    @BeforeAll
    static void allInit() {
        log.info("allInit():在所有方法前执行,只执行一次");
    }

    @BeforeEach
    void eachInit() {
        log.info("eachInit():在测试方法前执行,每个测试方法前都执行");
    }

    @Test
    void successTest() {
        log.info("successTest():方法执行成功");
    }

    @AfterEach
    void eachDown() {
        log.info("eachDown():在测试方法后执行,每个测试方法后都执行");
    }

    @AfterAll
    static void allDown() {
        log.info("allDown():在测试方法后执行,每个测试方法后都执行");
    }

}
    
java 复制代码
package com.artisan.boottest.example;

import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.time.Duration;

/**
 * @author 小工匠
 * @version 1.0
 * @description: JUnit断言测试
 * @mark: show me the code , change the world
 */
@Slf4j
@ExtendWith(SpringExtension.class)
public class AssertTest {


    // 可以使用fail方法直接断言方法执行失败并输出提示信息。
    @Test
    void failTest() {
        Assertions.fail("failTest():方法执行失败");
    }


    //  还可以通过assertTrue、assertNull、assertEquals这类方法来断言结果是否符合预期。
    @Test
    void trueTest(){
        Assertions.assertTrue(666==666);
    }

    @Test
    void trueFalse(){
        Assertions.assertFalse(8888<=9999);
    }

    @Test
    void nullTest(){
        String str = null;
        Assertions.assertNull(str);
    }

    @Test
    void notNullTest(){
        String str = "test";
        Assertions.assertNotNull(str);
    }

    @Test
    void equalsTest(){
        String str1 = "test";
        String str2 = "test";
        Assertions.assertEquals(str1,str2);
    }

    @Test
    void notEqualsTest(){
        String str1 = "test";
        String str2 = "test";
        Assertions.assertNotEquals(str1,str2);
    }

    // 也可以使用assertThrows方法来断言方法中抛出的异常。
    @Test
    void throwsTest(){
        Assertions.assertThrows(NullPointerException.class,()->{
            String str = null;
            log.info(str.toLowerCase());
        });
    }

    //  还可通过assertTimeout方法断言方法的执行时间。
    @Test
    void timeoutTest(){
        Assertions.assertTimeout(Duration.ofMillis(1000),()->{
            long sleepTime = 2000;
            ThreadUtil.sleep(sleepTime);
            log.info("timeoutTest():休眠{}毫秒",sleepTime);
        });
    }

    // 或者通过assertAll方法将几个断言结合起来使用,Assertions类中提供的工具方法很多,具体可以参考它的代码。

    @Test
    void assertAllTest(){
        Assertions.assertAll(()->{
            trueTest();
        },()->{
            nullTest();
        },()->{
            equalsTest();
        });
    }




}
    
java 复制代码
package com.artisan.boottest.example;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 其他常用的注释
 * @mark: show me the code , change the world
 */
@Slf4j
@ExtendWith(SpringExtension.class)
public class OthreTest {

    // Spring Boot Test除了上述测试功能,还可以使用@Disabled来禁用某个测试方法
    @Test
    @Disabled("用于测试@Disabled注解")
    void disabledTest() {
        log.info("disabledTest():方法被执行");
    }

    // 也可以使用@RepeatedTest来实现循环测试
    private static int count = 0;
    @RepeatedTest(5)
    void repeatedTest() {
        count++;
        log.info("repeatedTest():重复执行第{}次",count);
    }

    // 还可以通过@ParameterizedTest来进行参数化测试
    @ParameterizedTest
    @ValueSource(ints = {1,2,3})
    public void parameterizedTest(int a){
        log.info("parameterizedTest():a={}",a);
    }



}
    

【三层测试】

java 复制代码
package com.artisan.boottest.project;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;

/**
 * @author 小工匠
 * @version 1.0
 * @description: Dao层方法测试
 * @mark: show me the code , change the world
 */
@Slf4j
@SpringBootTest
public class MapperTest {

    @Autowired
    private PmsBrandMapper brandMapper;

    @Test
    void testGetById(){
        long id = 6;
        PmsBrand pmsBrand = brandMapper.selectByPrimaryKey(id);
        LOGGER.info("brand name:{}",pmsBrand.getName());
        Assertions.assertEquals("小米",pmsBrand.getName());
    }
}
    
java 复制代码
package com.artisan.boottest.project;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @author 小工匠
 * @version 1.0
 * @description: Service层方法测试
 * @mark: show me the code , change the world
 */
@Slf4j
@SpringBootTest
public class ServiceTest {


    @Autowired
    private PmsBrandService brandService;

    @Test
    void testGetById(){
        long id = 6;
        PmsBrand pmsBrand = brandService.getBrand(id);
        log.info("brand name:{}",pmsBrand.getName());
        Assertions.assertEquals("小米",pmsBrand.getName());
    }
}
    
java 复制代码
package com.artisan.boottest.project;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

/**
 * @author 小工匠
 * @version 1.0
 * @description: 对于Controller层方法进行测试,有时我们需要模拟请求,使用MockMvc即可
 * @mark: show me the code , change the world
 */
@SpringBootTest
@AutoConfigureMockMvc
public class ControllerTest {


    @Autowired
    private MockMvc mockMvc;

    @Test
    void mvcTest() throws Exception {
        //模拟发送一个请求访问分页查询品牌列表的接口
        mockMvc.perform(MockMvcRequestBuilders.get("/brand/list") //设置请求地址
                        .param("pageNum", "1") //设置请求参数
                        .param("pageSize", "5"))
                .andExpect(MockMvcResultMatchers.status().isOk()) //断言返回状态码为200
                .andDo(MockMvcResultHandlers.print()) //在控制台打印日志
                .andReturn(); //返回请求结果
    }
}
    
相关推荐
zimoyin5 小时前
Kotlin 使用 Springboot 反射执行方法并自动传参
spring boot·后端·kotlin
LiuYuHani7 小时前
Spring Boot面试题
java·spring boot·后端
电脑玩家粉色男孩7 小时前
八、Spring Boot 日志详解
java·spring boot·后端
ChinaRainbowSea8 小时前
八. Spring Boot2 整合连接 Redis(超详细剖析)
java·数据库·spring boot·redis·后端·nosql
小咕聊编程9 小时前
【含文档+PPT+源码】基于小程序的智能停车管理系统设计与开发
java·spring boot·小程序
命运之手11 小时前
[ Spring ] Spring Boot Mybatis++ 2025
spring boot·spring·mybatis·mybatis-plus·mybatis++
计算机-秋大田12 小时前
基于微信小程序的实习记录系统设计与实现(LW+源码+讲解)
vue.js·spring boot·后端·微信小程序·小程序·课程设计
飞翔的佩奇12 小时前
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的疾病防控综合管理系统(含源码+数据库+毕业论文)
java·数据库·spring boot·mysql·spring·毕业设计·疾病防控
栗豆包13 小时前
w187社区养老服务平台的设计与实现
java·spring boot·后端·spring·tomcat
violin-wang13 小时前
如何在Intellij IDEA中识别一个文件夹下的多个Maven module?
java·spring boot·spring·maven·intellij-idea