# 06.SpringBoot-单元测试

# 1.Junit5简介

JUnit 5官方文档 (opens new window)

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。

  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的测试引擎。

使用 Junit 5 需要添加的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
1
2
3
4
5

Spring的JUnit 5的基本单元测试模板

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;//注意不是org.junit.Test(这是JUnit4版本的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBootApplicationTests {

    @Autowired
    private Component component;

    @Test
    //@Transactional 标注后连接数据库有回滚功能
    public void contextLoads() {
        Assertions.assertEquals(5, component.getFive());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.常用注解

  • @Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

  • @RepeatedTest:表示方法可重复执行。

  • @DisplayName:为测试类或者测试方法设置展示名称。

  • @BeforeEach:表示在每个单元测试之前执行。

  • @AfterEach:表示在每个单元测试之后执行。

  • @BeforeAll:表示在所有单元测试之前执行。

  • @AfterAll:表示在所有单元测试之后执行。

  • @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。

  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误。

  • @ExtendWith:为测试类或测试方法提供扩展类引用。

  • @ParameterizedTest:表示方法是参数化测试。

测试示例:

/*
 * SpringBootTest包括 @BootstrapWith(SpringBootTestContextBootstrapper.class)
 *   @ExtendWith({SpringExtension.class})
 *   标上此注解,可以使用@Autowired注入Bean对象
 */
@SpringBootTest
@DisplayName("Junit5的注解测试") //测试命名
public class Junit5Test {

    @Autowired
    private JdbcTemplate jdbcTemplate; 

    @RepeatedTest(3)
    @DisplayName("测试方法1") //测试命名
    public void test1() {
        System.out.println("test1");
    }

    @Timeout(1)
    @DisplayName("测试方法2")
    @Test
    public void test2() throws InterruptedException {
        Thread.sleep(1500);
        System.out.println("test2");
    }

    @Disabled
    @DisplayName("测试方法3")
    @Test
    public void test3() {
        System.out.println("test3");
    }

    @DisplayName("测试方法4")
    @Test
    public void test4() {
        System.out.println(jdbcTemplate);
    }

    @BeforeEach
    public void beforeEach() {
        System.out.println("测试开始了");
    }

    @AfterEach
    public void afterEach() {
        System.out.println("测试结束了");
    }

    @BeforeAll
    public static void beforeAll() {
        System.out.println("所有测试开始");
    }

    @AfterAll
    public static void afterAll() {
        System.out.println("所有测试结束");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 3.断言机制

断言Assertion是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。

JUnit 5 内置的断言可以分成如下几个类别:

# 简单断言

用来对单个值进行简单的验证。如:

    @Test 
    @DisplayName("简单断言")
    public void simpleAssert(){
        assertEquals(1,1);
        assertNotEquals(1,2);

        Object o = new Object();
        Object o1 = new Object();

        assertSame(o,o);
        assertNotSame(o,o1);

        assertFalse(false);
        assertTrue(true);

        assertNull(null);
        assertNotNull(o);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 数组断言

通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。

    @DisplayName("数组断言")
    @Test
    public void array(){
        int[] a1 = {1, 2, 3};
        int[] a2 = {1, 2, 4};
        assertArrayEquals(a1,a2);
    }
1
2
3
4
5
6
7

# 组合断言

assertAll()方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。

    @DisplayName("组合断言")
    @Test
    public void all(){
        assertAll("test",
                ()->assertEquals(1,1),()->assertTrue(true));
    }
1
2
3
4
5
6

# 异常断言

    @DisplayName("异常断言")
    @Test
    public void exception(){
        assertThrows(ArithmeticException.class,()->{
            int i=10/0;
        });
    }
1
2
3
4
5
6
7

# 快速失败

只要执行到fail语句就会失败

    @DisplayName("快速失败")
    @Test
    public void unSuccess(){
        if (1==2){
            fail("失败了");
        }
    }
1
2
3
4
5
6
7

# 超时断言

JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。

    @DisplayName("超时断言")
    @Test
    public void timeout(){
        assertTimeout(Duration.ofSeconds(1),()->{
            Thread.sleep(2000);
        });
    }
1
2
3
4
5
6
7

# 4.前置条件

Junit 5中的前置条件(assumption)类似于断言,不同之处在于不满足的断言会使测试方法失败,而不满足的前置条件只会是的测试方法的执行终止,而且他执行不成功的图标与@Disabled 禁用的图标一致

前置条件可以看成测试方法的前提,不满足时就没有执行的必要

@DisplayName("前置条件")
public class AssumptionsTest {
    private final String environment = "DEV";

    @DisplayName("测试方法1")
    @Test
    void test1(){
        assumeTrue(Objects.equals(environment, "DEV"));
        assumeFalse(Objects.equals(environment, "PROD"));
    } 
    @DisplayName("assume then do")
    @Test
    public void assumeThenDo() {
        assumingThat(
                Objects.equals(this.environment, "DEV"),
                () -> System.out.println("In DEV")
        );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 5.嵌套循环

官方文档 - Nested Tests (opens new window)

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach注解,而且嵌套的层次没有限制,但是外部不可以使用内部的

package com.demo.boot;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.EmptyStackException;
import java.util.Stack;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

# 6.参数化测试

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

  • @NullSource: 表示为参数化测试提供一个null的入参

  • @EnumSource: 表示为参数化测试提供一个枚举入参

  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

package com.demo.boot;


import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.stream.Stream;

public class parameterized {

    @ParameterizedTest
    @ValueSource(ints ={1,3,4,5})
    @DisplayName("测试参数化1")
    public void test1(int i){
        System.out.println(i);
    }

    @ParameterizedTest
    @MethodSource("stringProvider")
    @DisplayName("测试参数化2")
    public void test2(String str){
        System.out.println(str);
    }

    static Stream<String>  stringProvider(){
        return Stream.of("apple","banana");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |