跳转至

关于JUnit 5

约 785 个字 151 行代码 预计阅读时间 5 分钟

JUnit 5 是目前 Java 单元测试领域的主流框架,它由三个子项目组成:

  1. JUnit Platform:用于在 JVM 上启动测试框架的运行平台。
  2. JUnit Jupiter:提供了编写 JUnit 5 测试所需的 API 和测试引擎。
  3. JUnit Vintage:用于兼容运行 JUnit 3 和 JUnit 4 的测试。

相比 JUnit 4,JUnit 5 基于 Java 8 编写,支持 Lambda 表达式、参数化测试、嵌套测试、扩展机制等特性。

依赖引入

普通 Maven 项目推荐直接引入聚合依赖 junit-jupiter,下面的示例使用的是当前较新的稳定版本 5.11.0

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<properties>
    <junit-jupiter.version>5.11.0</junit-jupiter.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>${junit-jupiter.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

该聚合依赖已经包含了 junit-jupiter-apijunit-jupiter-paramsjunit-jupiter-engine

为了确保 Maven 能够正确识别并执行 JUnit 5 测试,建议同时配置较新版本的 maven-surefire-plugin

XML
1
2
3
4
5
6
7
8
9
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
        </plugin>
    </plugins>
</build>

核心注解

注解 说明
@Test 标记一个测试方法
@BeforeAll 在所有测试方法执行前执行一次,需标注在 static 方法上
@BeforeEach 在每个测试方法执行前执行
@AfterEach 在每个测试方法执行后执行
@AfterAll 在所有测试方法执行后执行一次,需标注在 static 方法上
@DisplayName("...") 自定义测试显示名称
@Disabled 禁用该测试方法或测试类
@Nested 声明嵌套测试类
@Tag("fast") 为测试打标签,便于按标签选择执行
@Timeout 限制测试方法的执行时间
@ExtendWith(...) 注册扩展,例如 Spring 的 SpringExtension
@RepeatedTest(n) 重复执行测试 n

例如下面的生命周期示例:

Java
 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
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

@DisplayName("生命周期演示")
class LifecycleDemoTest {

    @BeforeAll
    static void setupAll() {
        System.out.println("所有测试开始前的初始化");
    }

    @BeforeEach
    void setup() {
        System.out.println("每个测试前执行");
    }

    @Test
    @DisplayName("测试加法")
    void testAdd() {
        assertEquals(5, 2 + 3);
    }

    @AfterEach
    void tearDown() {
        System.out.println("每个测试后执行");
    }

    @AfterAll
    static void tearDownAll() {
        System.out.println("所有测试结束后的清理");
    }
}

常用断言

JUnit 5 的断言类位于 org.junit.jupiter.api.Assertions

断言 说明
assertEquals(expected, actual) 判断两个值是否相等
assertTrue(condition) 判断条件是否为 true
assertFalse(condition) 判断条件是否为 false
assertNull(object) 判断对象是否为 null
assertNotNull(object) 判断对象是否不为 null
assertThrows(Exception.class, executable) 判断是否抛出了指定异常
assertAll("描述", ...) 组合多个断言,全部执行后汇总失败信息
assertTimeout(duration, executable) 判断代码执行是否超时
fail("失败原因") 手动标记测试失败

例如下面的断言示例:

Java
 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
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class AssertionsDemoTest {

    @Test
    void basicAssertions() {
        assertEquals(4, 2 + 2);
        assertTrue(5 > 3);
        assertNull(null);
    }

    @Test
    void groupedAssertions() {
        String name = "John";
        assertAll("用户信息校验",
                () -> assertEquals("John", name),
                () -> assertTrue(name.startsWith("J"))
        );
    }

    @Test
    void exceptionTesting() {
        Exception exception = assertThrows(ArithmeticException.class,
                () -> { int result = 1 / 0; });
        assertEquals("/ by zero", exception.getMessage());
    }
}

参数化测试

参数化测试需要依赖 junit-jupiter-params,如果已经引入了 junit-jupiter 聚合依赖,则无需额外引入。

常用参数来源注解

注解 说明
@ValueSource 提供单一类型的简单参数数组
@CsvSource 通过 CSV 格式提供多参数
@CsvFileSource 从 CSV 文件读取参数
@MethodSource 通过方法返回参数流
@EnumSource 从枚举取值
@NullSource / @EmptySource / @NullAndEmptySource 提供 null 或空值参数

@ValueSource 示例

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class ValueSourceDemoTest {

    @ParameterizedTest
    @ValueSource(strings = {"abc", "def", "ghi"})
    void testStringLength(String str) {
        assertTrue(str.length() > 0);
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 3, 5, 7})
    void testOddNumbers(int number) {
        assertTrue(number % 2 != 0);
    }
}

@CsvSource 示例

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;

class CsvSourceDemoTest {

    @ParameterizedTest(name = "{index} => {0} + {1} = {2}")
    @CsvSource({
            "1, 2, 3",
            "5, 5, 10",
            "-1, 1, 0"
    })
    void testAdd(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}

@MethodSource 示例

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;

class MethodSourceDemoTest {

    @ParameterizedTest
    @MethodSource("provideStrings")
    void testStringLength(String str, int expectedLength) {
        assertEquals(expectedLength, str.length());
    }

    static Stream<Arguments> provideStrings() {
        return Stream.of(
                Arguments.of("hello", 5),
                Arguments.of("junit5", 6),
                Arguments.of("", 0)
        );
    }
}

空值和 null 值示例

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class NullEmptyDemoTest {

    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = {" ", "abc"})
    void testStrings(String str) {
        assertNotNull(str);
    }
}

与 JUnit 4 的区别

JUnit 4 JUnit 5
import org.junit.Test import org.junit.jupiter.api.Test
@BeforeClass @BeforeAll
@Before @BeforeEach
@After @AfterEach
@AfterClass @AfterAll
@Ignore @Disabled
@RunWith @ExtendWith
ExpectedExceptionexpected assertThrows(...)

运行测试

编写好测试类后,可以通过以下方式运行:

  1. 在 IDE 中右键测试类或测试方法,选择运行。
  2. 在项目根目录执行 mvn test,Maven 会通过 maven-surefire-plugin 自动发现并执行测试。

Note

如果测试没有被执行,首先检查 maven-surefire-plugin 版本是否过低,其次确认测试类的访问修饰符是否为 public 或包级私有(JUnit 5 支持包级私有)。