1. 概述

本教程将指导如何从 JUnit 4 迁移到最新的 JUnit 5 版本,并对比两个版本的核心差异。关于 JUnit 5 的基础使用指南,可参考我们的另一篇文章

2. JUnit 5 的优势

先看看 JUnit 4 的明显局限性:

  • 单一 jar 包问题:整个框架打包在一个 jar 中,即使只需要部分功能也得全量导入。JUnit 5 支持按需引入模块,粒度更细
  • 测试运行器限制:JUnit 4 同时只能运行一个测试 runner(如 SpringJUnit4ClassRunnerParameterized)。JUnit 5 允许多个 runner 并行工作
  • Java 版本滞后:JUnit 4 停滞在 Java 7,错失 Java 8+ 新特性。JUnit 5 充分利用了 Java 8+ 的特性

JUnit 5 的设计目标就是通过完全重写,解决上述痛点。

3. 核心差异

JUnit 4 被拆解为 JUnit 5 的以下模块:

  • JUnit Platform:负责测试执行、发现和报告的扩展框架基础层。
  • JUnit Vintage:提供对 JUnit 4/3 的向后兼容支持。

3.1. 注解变更

JUnit 5 对注解做了重大调整。测试方法需使用 @org.junit.jupiter.api.Test 替代 @org.junit.Test

关键变化:@Test 注解不再支持异常和超时参数

JUnit 4 的异常测试:

@Test(expected = Exception.class)
public void shouldRaiseAnException() throws Exception {
    // ...
}

JUnit 5 改用 assertThrows

@Test
void shouldRaiseAnException() throws Exception {
    Assertions.assertThrows(Exception.class, () -> {
        //...
    });
}

JUnit 4 的超时测试:

@Test(timeout = 1)
public void shouldFailBecauseTimeout() throws InterruptedException {
    Thread.sleep(10);
}

JUnit 5 改用 assertTimeout

@Test
void shouldFailBecauseTimeout() throws InterruptedException {
    Assertions.assertTimeout(Duration.ofMillis(1), () -> Thread.sleep(10));
}

其他注解变更对照表:

  • @Before@BeforeEach
  • @After@AfterEach
  • @BeforeClass@BeforeAll
  • @AfterClass@AfterAll
  • @Ignore@Disabled

3.2. 断言增强

JUnit 5 支持懒加载断言消息(避免不必要的字符串拼接):

@Test
void shouldFailBecauseTheNumbersAreNotEqual_lazyEvaluation() {
    Assertions.assertTrue(
      2 == 3, 
      () -> "Numbers " + 2 + " and " + 3 + " are not equal!");
}

新增分组断言(一次性报告所有失败):

@Test
void shouldAssertAllTheGroup() {
    List<Integer> list = Arrays.asList(1, 2, 4);
    Assertions.assertAll("List is not incremental",
        () -> Assertions.assertEquals(list.get(0).intValue(), 1),
        () -> Assertions.assertEquals(list.get(1).intValue(), 2),
        () -> Assertions.assertEquals(list.get(2).intValue(), 3));
}

3.3. 假设机制

Assumptions 类迁移至 org.junit.jupiter.api.Assumptions。除保留 JUnit 4 的功能外,新增条件化断言:

@Test
void whenEnvironmentIsWeb_thenUrlsShouldStartWithHttp() {
    assumingThat("WEB".equals(System.getenv("ENV")),
      () -> assertTrue("http".startsWith(address)));
}

3.4. 标签与过滤

JUnit 4 使用 @Category 分组测试,JUnit 5 改用 @Tag

@Tag("annotations")
@Tag("junit5")
class AnnotationTestExampleUnitTest {
    /*...*/
}

通过 Maven Surefire 插件过滤标签:

<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <properties>
                    <includeTags>junit5</includeTags>
                </properties>
            </configuration>
        </plugin>
    </plugins>
</build>

3.5. 测试运行注解

JUnit 4 的 @RunWith 用于集成框架或改变执行流程,JUnit 5 用 @ExtendWith 替代。

Spring 集成示例对比:

JUnit 4 写法:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  {"/app-config.xml", "/test-data-access-config.xml"})
public class SpringExtensionTest {
    /*...*/
}

JUnit 5 写法:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
  { "/app-config.xml", "/test-data-access-config.xml" })
class SpringExtensionTest {
    /*...*/
}

3.6. 测试规则迁移

JUnit 4 的 @Rule@ClassRule 在 JUnit 5 中可通过 @ExtendWith 实现。

以日志追踪规则为例:

JUnit 4 实现:

public class TraceUnitTestRule implements TestRule {
    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                // 测试前后日志逻辑
                ...
            }
        };
    }
}

// 测试类中使用
@Rule
public TraceUnitTestRule traceRuleTests = new TraceUnitTestRule();

JUnit 5 实现(更直观):

public class TraceUnitExtension implements AfterEachCallback, BeforeEachCallback {
    @Override
    public void beforeEach(TestExtensionContext context) throws Exception {
        // 前置逻辑
    }

    @Override
    public void afterEach(TestExtensionContext context) throws Exception {
        // 后置逻辑
    }
}

// 测试类中使用
@ExtendWith(TraceUnitExtension.class)
class RuleExampleUnitTest {
    @Test
    void whenTracingTests() {
        /*...*/
    }
}

3.7. JUnit Vintage 兼容层

通过 JUnit Vintage 模块,可在 JUnit 5 环境中运行 JUnit 3/4 测试。添加依赖:

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <version>${junit5.vintage.version}</version>
    <scope>test</scope>
</dependency>

4. 总结

JUnit 5 通过模块化设计和现代化改造,解决了 JUnit 4 的核心痛点。本文对比了两个版本的关键差异,并提供了迁移路径。完整代码示例可在 GitHub 获取。

⚠️ 迁移建议:优先使用 JUnit Vintage 运行旧测试,逐步将新测试迁移至 JUnit 5 原生 API。


« 上一篇: Chronicle Queue 介绍
» 下一篇: Awaitility 介绍