从 JUnit 4 迁移到 JUnit 5 :重要的差异及益处 (QFY译版)

从JUnit 4迁移到JUnit 5:重要的区别和好处
提升及新特性使JUnit 5引人注目。
  by Brian McGlauflin
  April 6, 2020
  原文: https://blogs.oracle.com/javamagazine/migrating-from-junit-4-to-junit-5-important-differences-and-benefits

JUnit 5 是 JUnit 框架强大而灵活的更新,它提供了各种改进和新特性来组织和描述测试用例,以及帮助理解测试结果。更新到 JUnit 5 快速而简单的:只需更新项目依赖项并开始使用新特性。 如果您已经使用过一段时间 JUnit 4 ,迁移测试可能看起来是一项艰巨的任务。好消息是,您可能不需要转换任何测试;JUnit5可以使用 Vintage 库运行JUnit4测试。 也就是说,在JUnit5中开始编写新的测试有四个坚实的理由:

  • JUnit 5 利用了Java 8或更高版本的特性,比如lambda函数,使得测试更加强大,更易于维护。

  • JUnit 5为描述、组织和执行测试添加了一些非常有用的新特性。例如,测试得到更好的显示名称,并且可以按层次结构组织。

  • JUnit5 被组织到多个库中,因此可以只导入需要的库到项目中。使用Maven和Gradle这样的构建系统,很容易引入正确的库。

  • JUnit 5 一次可以使用多个扩展,而JUnit 4不行(一次只能使用一个runner)。这意味着您可以轻松地将Spring扩展与其他扩展(例如您自己的自定义扩展)结合起来。

从JUnit 4切换到JUnit 5非常简单,即使您已经有了 JUnit 4 测试。大多数组织不需要将老的JUnit测试转换为JUnit 5,除非需要使用新的特性。如果是这样,请使用以下步骤:

  1. 更新并构建从 JUnit 4 到 JUnit 5 的系统. 确保在测试运行时路径中引入 junit-vintage-engine jar包,以允许执行现有的测试。

  2. 开始使用新的JUnit 5构造构建新的测试。

  3. (可选)将JUnit测试转换为JUnit 5

重要区别

JUnit5测试看起来基本上与JUnit4测试相同,但是有一些差异应该注意。

导入.

JUnit 5 将注解及类放在新的 org.junit.jupiter 包中。例如 org.junit.Test 变成了 org.junit.jupiter.api.Test

注解.

@Test 注解不再有参数;每个参数都已移动到一个函数中。例如,下面是JUnit4中会引发异常的测试:

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

在JUnit 5中,改成了这样:

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

同样,超时也改了。下面是JUnit 4中的一个例子:

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

在JUnit 5中,改成了:

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

以下是已更改的其它注解:

  • @Before 变成了 @BeforeEach.

  • @After 变成了 @AfterEach.

  • @BeforeClass 变成了 @BeforeAll.

  • @AfterClass 变成了 @AfterAll.

  • @Ignore 变成了 @Disabled.

  • @Category 变成了 @Tag.

  • @Rule@ClassRule 废弃了; 用 @ExtendWith@RegisterExtension 替代.

断言.

JUnit 5断言现在 org.junit.jupiter.api.Assertions 中。大多数常见的断言,如 assertEquals()assertNotNull() ,看起来都和以前一样,但是有一些不同:

  • 错误消息移到了最后一个参数,例如: assertEquals("my message", 1, 2) 改成了 assertEquals(1, 2, "my message")

  • 大多数断言现在接受一个 lambda 来构造错误消息,只有当断言失败时才会调用该消息。

  • assertTimeout()assertTimeoutPreemptively() 已经替换了 @Timeout 注解 (JUnit 5中有一个 @Timeout 注解,但它的工作方式与JUnit 4不同).

  • 有几个新的断言,如下所述。 NOTE: 如果您愿意的话,可以在JUnit 5测试中继续使用JUnit 4中的断言.

Assumptions.

Assumptions 已移动到 org.junit.jupiter.api.Assumptions. assumptions 都还在,但它们现在支持 BooleanSupplierHamcrest matchers 来匹配条件。Lambdas(可执行类型)可用于在满足条件时执行。 例如,JUnit4中有一个例子:

@Test
public void testNothingInParticular() throws Exception {
    Assume.assumeThat("foo", is("bar"));
    assertEquals(...);
}

在JUnit 5中,它变成:

@Test
void testNothingInParticular() throws Exception {
    Assumptions.assumingThat("foo".equals(" bar"), () -> {
        assertEquals(...);
    });
}

扩展JUnit

JUnit4中,定制框架通常意味着使用 @RunWith 注解来指定运行程序。使用多个 runners 是有问题的,通常需要运行链或使用 @Rule 。在JUnit 5中,使用扩展对其进行了简化和改进。 例如,JUnit 4 用 Spring 框架搭建测试是这样的:

@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {
    // ...
}

JUnit5,您可以替换为引入 Spring 扩展:

@ExtendWith(SpringExtension.class)
class MyControllerTest {
    // ...
}

@ExtendWith 注解是可重复的,这意味着可以轻松组合多个扩展。 您还可以创建一个类来实现 org.junit.jupiter.api.extension 中的接口,然后使用 @ExtendWith 将其添加到测试中,从而轻松定义自己的扩展。

将测试转换到 JUnit5

要将现有 JUnit 4测试转换到JUnit 5,请使用以下步骤,这些步骤应该适用于大多数测试:

  1. 删除JUnit 4 包 import 使用 JUnit 5 包 import 。例如,更新 @Test 注解的包名,并更新断言的包名和类名。现在还不必担心是否有编译错误,因为完成以下步骤应该可以解决它们。

  2. 全局用新注解及类名替换旧的。例如, @Before 替换为 @BeforeEachAsserts 替换为 Assertions

  3. 更新 assertions ;断言的消息参数需要移到末尾(当所有三个参数都是字符串时,请特别注意!)。另外,更新超时和预期的异常(参见上面的示例)。

  4. 如果使用了 assumptions ,请更新它们。

  5. 用适当的 @ExtendWith 替换 @RunWith, @Rule, or @ClassRule 。您可能需要在网上找找扩展示例的更新文档。

Note
迁移参数化测试将需要更多的重构,特别是如果您使用了JUnit 4参数化(JUnit 5参数化测试的格式更接近JUnitParams)。

新功能

目前为止,我只讨论了现有的功能以及它有什么改变。但是JUnit 5提供了很多新特性,使您的测试更具描述性和可维护性。

显示名. 使用 JUnit 5,您可以将 @DisplayName 注解添加到类和方法中。生成报告时使用该名称,这样可以更容易地描述测试和跟踪失败的目的,例如:

@DisplayName("Test MyClass")
class MyClassTest {
    @Test
    @DisplayName("Verify MyClass.myMethod returns true")
    void testMyMethod() throws Exception {
        // ...
    }
}

您也可以使用显示名称生成器来生成任何喜欢的测试名称。 有关详细信息和示例,请参阅junit文档

Assertions. JUnit 5引入了一些新的断言,例如:

  • assertIterableEquals() 对容器内每个元素验证 equals().

  • assertLinesMatch() 验证两个字符串列表是否匹配;它接受 expected 参数中的正则表达式。

  • assertAll() 将多个断言分组在一起。附加的好处是,即使个别断言失败,也会执行所有断言。

  • assertThrows()assertDoesNotThrow() 替代了 @Test 注解的 expected 属性.

嵌套测试. JUnit 4中的测试套件很有用,但JUnit 5中的嵌套测试更易于设置和维护,它们更好地描述了测试组之间的关系,例如:

@DisplayName("Verify MyClass")
class MyClassTest {
    MyClass underTest;
    @Test
    @DisplayName("can be instantiated")
    public void testConstructor() throws Exception {
        new MyClass();
    }
    @Nested
    @DisplayName("with initialization")
    class WithInitialization {
        @BeforeEach
        void setup() {
            underTest = new MyClass();
            underTest.init("foo");
        }
        @Test
        @DisplayName("myMethod returns true")
        void testMyMethod() {
            assertTrue(underTest.myMethod());
        }
    }
}

上面的示例中,可以看到所有与 MyClass 相关的测试都在一个类中。我可以在外部测试类中验证该类是否可实例化,并为所有实例化和初始化 MyClass 的测试使用嵌套的内部类。@BeforeEach 方法仅适用于嵌套类中的测试。 测试和类的 @DisplayNames 注解指明测试的目的和组织。这有助于您理解测试报告,因为您可以看到执行测试的条件(Verify MyClass with initialization)和测试正在验证的内容( myMethod 返回true)。这是一个不错的 JUnit 5测试设计模式。

参数化测试. JUnit 4中就有测试参数化,有内置库,如 JUnit4Parameterized 或第三方库,如 JUnitParams 。在JUnit 5中,参数化测试是完全内置的,并采用了 JUnit4Parameterized 及 `JUnitParams`中的一些最佳特性,例如:

@ParameterizedTest
@ValueSource(strings = {"foo", "bar"})
@NullAndEmptySource
void myParameterizedTest(String arg) {
    underTest.performAction(arg);
}

格式类似于 JUnitParams ,参数直接传递给测试方法。注意,要测试的值可以来自多个不同的来源。这里,我只有一个参数,所以很容易使用 @ValueSource . @EmptySource@NullSource 表示要分别向要与之一起运行的值列表中添加空字符串和空值(如果同时使用这两个值,则可以将它们组合在一起,如上所示)。还有多个其他值源,如 @EnumSource@ArgumentsSource (自定义值提供程序)。如果需要多个参数,还可以使用 @MethodSource@CsvSource 。 JUnit 5中添加的另一个测试类型 @RepeatedTest ,能指定测试重复的次数。

条件执行测试. JUnit 5提供了 ExecutionCondition 扩展API,可以有条件地启用或禁用测试或容器(测试类)。这就像在测试中使用 @Disabled ,但它可以自定义条件。有多种内置条件,例如:

  • @EnabledOnOs@DisabledOnOs : 仅在指定的操作系统上启用或禁用测试

  • @EnabledOnJre and @DisabledOnJre : 在特定版本Java启用或禁用测试

  • @EnabledIfSystemProperty: 基于JVM系统属性的值启用测试

  • @EnabledIf: 如果满足脚本化条件,则使用脚本化逻辑启用测试

测试模板. 测试模板不是常规测试;它们定义了一组要执行的步骤,然后可以使用特定的调用上下文在其他地方执行这些步骤。这意味着您可以定义一次测试模板,然后在运行时构建一个调用上下文列表以运行该测试。 有关详细信息和示例,请参阅文档

动态测试. 动态测试类似于测试模板;要运行的测试在运行时生成。虽然测试模板是用一组特定的步骤定义的,并且运行多次,但是动态测试使用相同的调用上下文,但是可以执行不同的逻辑。动态测试的一个用途是流式处理抽象对象列表,并根据每个对象的具体类型为它们执行一组独立的断言。 文档中有很好的示例

结论

尽管您可能不需要将旧的JUnit 4测试转换为JUnit 5,除非您想使用新的junit5特性,但是有充分的理由切换到junit5。例如,JUnit 5测试更强大,更易于维护。此外,JUnit 5还提供了许多有用的新特性,只导入您使用的特性,您可以使用多个扩展,甚至可以创建自己的自定义扩展。这些变化和新特性的更新将JUnit框架变得更强大更灵活。

数码
沪ICP备19006215号-4