Logo

一文教你如何从 JUnit 4 迁移到 JUnit 5

一、概述

在本教程中,我们将学习如何从 JUnit 4 迁移到最新的 JUnit 5 版本,并介绍两个版本之间的差异。

二、JUnit 5 的优势

让我们从以前的版本 JUnit 4 开始介绍,它有一些明显的限制:

  • 即使我们只需要 JUnit 4 中的一个特定的功能,我们也需要导入整个框架。 在 JUnit 5 中支持按需导入部分内容。
  • 在 JUnit 4 中一次只有一个测试 Runner 可以执行测试(例如 SpringJUnit4ClassRunnerParameterized )。 JUnit 5 允许多个 Runner 同时运行。
  • JUnit 4 错过了很多 Java 8 特性。JUnit 5 充分利用了 Java 8 特性。

JUnit 5 背后的想法是完全重写 JUnit 4 以消除上述缺点。

三、差异

JUnit 5 的模块:

  • JUnit Platform -- 此模块涵盖了我们可能感兴趣的所有扩展框架:测试执行、发现和报告。
  • JUnit Vintage -- 此模块允许向后兼容 JUnit 4 甚至 JUnit 3。

3.1 注释

JUnit 5 对注释进行了重要更新。 最重要的一点是我们不能再使用 @Test 注释来指定期望。

JUnit 4 中的 expected 参数:

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

现在我们可以使用方法assertThrows

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

JUnit 4 中的 timeout 属性:

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

现在 JUnit 5 中的 assertTimeout 方法:

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

以下是在 JUnit 5 中的一些更新:

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

3.2 断言

我们还可以在 JUnit 5 的 lambda 中编写断言消息,以便跳过复杂的消息构造:

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

此外,我们可以在 JUnit 5 中对断言进行分组:

@Test
public 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 5 完全支持 JUnit 4 中现有的假设方法,并且还添加了一组新方法,允许我们仅在特定场景下运行一些断言:

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

3.4 标记和过滤

在 JUnit 4 中,我们可以使用 @Category 注释对测试进行分组。在 JUnit 5 中,@Category 注释被 @Tag 注释替换:

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

我们可以使用 maven-surefire-plugin 包含/排除特定标签:

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

3.5 运行测试的新注释

在 JUnit 4 中,我们使用 @RunWithannotation 将测试上下文与其他框架集成,或者更改测试用例中的整体执行流程。

在 JUnit 5 中,我们现在可以使用 @ExtendWith 注释来提供类似的功能。

例如,要使用 JUnit 4 中的 Spring 功能:

@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" })
public 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 {
                // Before and after an evaluation tracing here 
                ...
            }
        };
    }
}

我们在一个测试套件中实现它:

@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 {
        // ...
    }
}

使用 org.junit.jupiter.api.extension 包中提供的 JUnit 5 的 AfterEachCallbackBeforeEachCallback 接口,我们可以在测试套件中轻松实现此规则:

@ExtendWith(TraceUnitExtension.class)
public class RuleExampleTest {
 
    @Test
    public void whenTracingTests() {
        /*...*/
    }
}

3.7 JUnit 5 Vintage

JUnit Vintage 通过在 JUnit 5 上下文中运行 JUnit 3 或 JUnit 4 测试来帮助迁移 JUnit 测试。

我们可以通过导入 JUnit Vintage Engine 来使用它:

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

四、结论

JUnit 5 是对 JUnit 4 框架的模块化和现代版。在本文中,我们介绍了这两个版本之间的主要区别,并展示了如何从一个版本迁移到另一个版本。

本文的完整实现可以在 GitHub 中找到。

分享内容