1. 概述
本教程将指导如何从 JUnit 4 迁移到最新的 JUnit 5 版本,并对比两个版本的核心差异。关于 JUnit 5 的基础使用指南,可参考我们的另一篇文章。
2. JUnit 5 的优势
先看看 JUnit 4 的明显局限性:
- 单一 jar 包问题:整个框架打包在一个 jar 中,即使只需要部分功能也得全量导入。JUnit 5 支持按需引入模块,粒度更细。
- 测试运行器限制:JUnit 4 同时只能运行一个测试 runner(如
SpringJUnit4ClassRunner
或Parameterized
)。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。