JUnit 5 教程:从入门到精通,掌握现代Java单元测试
JUnit 5 教程
在本 JUnit 教程中,我们将通过示例介绍 JUnit 5 的基础知识和新功能。在 Java 世界中,JUnit 是用于对 Java 代码进行单元测试的流行框架之一。JUnit 主要帮助开发人员在 JVM 上测试自己的代码。
JUnit 5 架构

JUnit 平台
- 在 JVM 上启动测试框架
- 拥有 TestEngine API,用于构建可在 JUnit 平台上运行的测试框架
JUnit Jupiter(木星)
- 融合了用于编写测试的新编程模型和用于扩展的扩展模型
- 新增了诸如 @BeforeEach、@AfterEach、@AfterAll、@BeforeAll 等注解
JUnit Vintage(传统版)
- 支持在新平台上执行以前的 JUnit 3 和 4 版本测试
JUnit 的 Maven 依赖
为了在项目中实现基于 JUnit 5 的测试用例,需要将以下依赖项添加到项目的 pom.xml 文件中。
- JUnit 5 库
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
- JUnit 5 Maven Surefire 插件(用于在 IDE 不支持 JUnit 5 的情况下执行单元测试,如果 IDE 支持则无需此项)
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</plugin>
JUnit 5 新功能
运行时需要 Java 8 或更高版本。但是可以使用之前 Java 版本编译的代码进行测试。其中引入了各种新功能。
JUnit 注解
以下是一些常用的注解:
注解 | 描述 |
---|---|
@Test | 表示一个测试方法 |
@DisplayName | 为测试类或测试方法声明自定义显示名称 |
@BeforeEach | 表示带注解的方法应在每个测试方法之前执行 |
@AfterEach | 表示带注解的方法应在每个测试方法之后执行 |
@BeforeAll | 表示带注解的方法应在所有测试方法之前执行 |
@AfterAll | 表示带注解的方法应在所有测试方法之后执行 |
@Disable | 用于禁用测试类或测试方法 |
@Nested | 表示带注解的类是一个嵌套的、非静态的测试类 |
@Tag | 声明用于过滤测试的标签 |
@ExtendWith | 注册自定义扩展 |
package com.Olivia;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JUnit5Sample1Test {
@BeforeAll
static void beforeAll() {
System.out.println("**--- 在此类的所有测试方法执行前,仅执行一次 ---**");
}
@BeforeEach
void beforeEach() {
System.out.println("**--- 在此类的每个测试方法执行前执行 ---**");
}
@Test
void testMethod1() {
System.out.println("**--- 测试方法1 执行 ---**");
}
@DisplayName("带条件的测试方法2")
@Test
void testMethod2() {
System.out.println("**--- 测试方法2 执行 ---**");
}
@Test
@Disabled("实现待定")
void testMethod3() {
System.out.println("**--- 测试方法3 执行 ---**");
}
@AfterEach
void afterEach() {
System.out.println("**--- 在此类的每个测试方法执行后执行 ---**");
}
@AfterAll
static void afterAll() {
System.out.println("**--- 在此类的所有测试方法执行后,仅执行一次 ---**");
}
}

JUnit 断言
每个测试方法必须使用断言来对条件进行评估,以使测试能够继续执行。JUnit Jupiter 的断言存储在 org.junit.jupiter.api.Assertions
类中,所有方法都是静态的。
断言 | 描述 |
---|---|
assertEquals(expected, actual) |
当预期值不等于实际值时失败 |
assertFalse(expression) |
当表达式不为假时失败 |
assertNull(actual) |
当实际值不为 null 时失败 |
assertNotNull(actual) |
当实际值为 null 时失败 |
assertAll() |
将多个断言分组,即使其中一个或多个断言失败,所有断言也会被执行 |
assertTrue(expression) |
当表达式不为真时失败 |
assertThrows() |
预期被测试的类会抛出异常 |
@Test
void testAssertEqual() {
assertEquals("ABC", "ABC");
assertEquals(20, 20, "可选断言消息");
assertEquals(2 + 2, 4);
}
@Test
void testAssertFalse() {
assertFalse("FirstName".length() == 10);
assertFalse(10 > 20, "断言消息");
}
@Test
void testAssertNull() {
String str1 = null;
String str2 = "abc";
assertNull(str1);
assertNotNull(str2);
}
@Test
void testAssertAll() {
String str1 = "abc";
String str2 = "pqr";
String str3 = "xyz";
assertAll("数字",
() -> assertEquals(str1,"abc"),
() -> assertEquals(str2,"pqr"),
() -> assertEquals(str3,"xyz")
);
//取消注释以下代码并理解每个断言的执行
/*assertAll("数字",
() -> assertEquals(str1,"abc"),
() -> assertEquals(str2,"pqr1"),
() -> assertEquals(str3,"xyz1")
);*/
}
@Test
void testAssertTrue() {
assertTrue("FirstName".startsWith("F"));
assertTrue(10 < 20, "断言消息");
}
@Test
void testAssertThrows() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("发生非法参数异常");
});
assertEquals("发生非法参数异常", exception.getMessage());
}
JUnit5 的导入
它的测试类需要导入 org.junit.jupiter.api.Test
语句而不是 org.junit.Test
。此外,测试方法不需要公共修饰符和本地包。
import org.junit.jupiter.api.Test;
JUnit5 的假设
假设是 org.junit.jupiter.api.Assumptions
类中的静态方法。只有满足指定条件时,它们才会执行测试,否则测试将被中止。中止的测试不会导致构建失败。当假设失败时,会抛出 org.opentest4j.TestAbortedException
并跳过该测试。
假设 | 描述 |
---|---|
assumeTrue |
当肯定条件成立时执行 lambda 主体,否则测试将被跳过 |
assumeFalse |
当否定条件成立时执行 lambda 主体,否则测试将被跳过 |
assumingThat |
如果假设成立,测试方法的一部分将执行;assumingThat() 中 lambda 之后的所有内容都将执行,无论假设是否成立 |
@Test
void testAssumeTrue() {
boolean b = 'A' == 'A';
assumeTrue(b);
assertEquals("Hello", "Hello");
}
@Test
@DisplayName("test executes only on Saturday")
public void testAssumeTrueSaturday() {
LocalDateTime dt = LocalDateTime.now();
assumeTrue(dt.getDayOfWeek().getValue() == 6);
System.out.println("further code will execute only if above assumption holds true");
}
@Test
void testAssumeFalse() {
boolean b = 'A' != 'A';
assumeFalse(b);
assertEquals("Hello", "Hello");
}
@Test
void testAssumeFalseEnvProp() {
System.setProperty("env", "prod");
assumeFalse("dev".equals(System.getProperty("env")));
System.out.println("further code will execute only if above assumption hold");
}
@Test
void testAssumingThat() {
System.setProperty("env", "test");
assumingThat("test".equals(System.getProperty("env")),
() -> {
assertEquals(10, 10);
System.out.println("perform below assertions only on the test env");
});
assertEquals(20, 20);
System.out.println("perform below assertions on all env");
}
JUnit 嵌套测试类
嵌套测试允许创建嵌套类并执行其中的所有测试方法。内部类必须是非静态的。只需使用 @Nested
注解内部类,内部类中的所有测试方法都将被执行。
@BeforeAll
static void beforeAll() {
System.out.println("**--- JUnit5Sample4Test :: beforeAll :: 在所有测试方法执行前执行一次 ---**");
}
@BeforeEach
void beforeEach() {
System.out.println("**--- JUnit5Sample4Test :: beforeEach :: 在每个测试方法执行前执行 ---**");
}
@AfterEach
void afterEach() {
System.out.println("**--- JUnit5Sample4Test :: afterEach :: 在每个测试方法执行后执行 ---**");
}
@AfterAll
static void afterAll() {
System.out.println("**--- JUnit5Sample4Test :: afterAll :: 在所有测试方法执行后执行一次 ---**");
}
@Nested
class InnerClass {
@BeforeEach
void beforeEach() {
System.out.println("**--- InnerClass :: beforeEach :: 在每个测试方法执行前执行 ---**");
}
@AfterEach
void afterEach() {
System.out.println("**--- InnerClass :: afterEach :: 在每个测试方法执行后执行 ---**");
}
@Test
void testMethod1() {
System.out.println("**--- InnerClass :: testMethod1 :: 执行测试方法1 ---**");
}
@Nested
class InnerMostClass {
@BeforeEach
void beforeEach() {
System.out.println("**--- InnerMostClass :: beforeEach :: 在每个测试方法执行前执行 ---**");
}
@AfterEach
void afterEach() {
System.out.println("**--- InnerMostClass :: afterEach :: 在每个测试方法执行后执行 ---**");
}
@Test
void testMethod2() {
System.out.println("**--- InnerMostClass :: testMethod2 :: 执行测试方法2 ---**");
}
}
}
JUnit 测试异常
在某些情况下,方法有望根据特定条件抛出异常。如果给定的方法未抛出指定的异常,assertThrows
将导致测试失败。
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("非法参数异常发生");
});
assertEquals("非法参数异常发生", exception.getMessage());
JUnit 测试执行
单元测试可以通过多种方式来执行,其中两种方式如下:
- 使用 Eclipse IDE Oxygen.3a (4.7.3a) 版本,打开要执行的测试文件。右键单击文件并选择“运行方式 (Runs As)”,然后选择“JUnit 测试 (JUnit Test)”。
- 在 Windows 命令行中使用
mvn test
命令。
总结
我们已经通过一些例子探索了 JUnit 5 及其新特性。我们还了解了如何使用 JUnit 注解、断言、假设、异常以及编写嵌套测试类。
您可以从我们的 GitHub 仓库中下载完整的示例项目。