从JUnit4迁移到JUnit5:重要的区别和好处

Migrating from JUnit 4 to JUnit 5: Important Differences and Benefits

JUnit5简述

适应软件工程的发展,JUnit5在先前版本上演化出来,功能改进和提供新特性,为了更好的服务于测试。一般使用而言:只需更新依赖项即可。

JUnit5提供了Vintage库运行JUnit4的测试而无需对测试用例做任何修改。

junit-vintage-engine 是 JUnit 4 中使用的测试引擎
junit-jupiter-engine 是 JUnit 5 中使用的测试引擎

看完以下四点介绍,再决定是否使用JUnit5也不迟:

  • 使用了java语言高版本的特性,如lambda函数。使得测试更强大也更易于维护;
  • 为描述、组织、执行测试添加了一些非常有用的新特性,例如,测试可以获得更好的显示名称,并且可以分层组织;
  • 按照特性分成独立的依赖包,使用中只需导入需要的特性依赖即可,如使用Maven和Gradle构建的工程,导入依赖是很方便的;
  • 可以同时使用多个扩展,而JUnit4一次只能使用一个runner,这意味着可以很容易的将Spring扩展和其他扩展结合起来;

从JUnit4切换到JUnit5不是很麻烦,对不使用新特性而言,一般无需对旧的测试用例做多少更改,切换步骤如下:

  • 更新依赖项(maven或gradle的配置文件),注意要保留junit-vintage-engine依赖以运行JUnit4的测试用例;
  • 新的测试用例可以使用JUnit5构建编写了;

主要区别

两者使用起来差别不大,一些需要额外留意的如下:

路径

JUnit 5 使用新的包结构org.junit.jupiter.如org.junit.Test换成org.junit.jupiter.api.Test

注解

注解@Test不再有参数,参数移到方法体中。
如JUnit4中测试预期抛出异常样例:

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

在JUnit5中,使用如下:

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

同样的,常用参数timeout在JUnit4中如下:

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

在JUnit5中,使用如下:

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

常用注解变动如下:

JUnit4JUnit5
@Before@BeforeEach
@After@AfterEach
@BeforeClass@BeforeAll
@AfterClass@AfterAll
@Ignore@Disabled
@Category@Tag

@Rule@ClassRule 使用 @ExtendWith@RegisterExtension 替代。

断言

JUnit5中断言路径是org.junit.jupiter.api.Assertions。多数常见断言,如assertEquals(),assertNotNull(),虽然看起来没变,但是存在一些变动:

  • 参数中错误信息移到最后,如assertEquals("error msg", 1, 2) 变成assertEquals(1, 2, "error msg")
  • 多数断言支持lambda形式的错误信息,在断言失败的时候调用;
  • assertTimeout()assertTimeoutPreemptively()取代JUnit4中的@Timeout 注解(JUnit5中的@Timeout注解功能作用和JUnit4中的@Timeout是不一样的).

假设

假设的包路径是org.junit.jupiter.api.Assumptions。支持先前的假设及使用,同时提供了支持BooleanSupplierHamcrest matchers匹配条件,支持条件匹配上执行lambda表达式。如JUnit4中的一个例子:

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

在JUnit5中可以写成如下:

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

JUnit的扩展

在JUnit4中,一般使用注解@RunWIth指定运行环境,是JUnit提供给其他框架测试环境接口扩展。使用多个运行器是有问题的,通常需要chaining或者注解@Rule。在JUnit5中对扩展进行了简化和改进。
在JUnit4中使用spring框架构建测试如下:

@RunWith(SpringJUnit4ClassRunner.class)
public class MyControllerTest {
    // ...
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface RunWith {
    Class<? extends Runner> value();
}

在JUnit5中使用Spring extension替代:

@ExtendWith(SpringExtension.class)
class MyControllerTest {
    // ...
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(Extensions.class)
@API(
    status = Status.STABLE,
    since = "5.0"
)
public @interface ExtendWith {
    Class<? extends Extension>[] value();
}

注解@ExtendWith是可重复的, 意味着多个扩展可以方便的组合在一起。

如何自定义扩展呢?

通过实现包org.junit.jupiter.api.extension里的一个或多个接口,然后使用注解@ExtendWith添加到测试样例中。

换成JUnit5

将JUnit4测试用例转换到JUnit5,对多数用例而言一般步骤如下:

  • 修改包名路径,移除JUnit4,换成JUnit5。如注解@Test的路径,Asserts改成Assertions等;
  • 全局替换注解和类名,如@Before换成@BeforeEach,就使用IDE工具而言,更换了依赖后,旧的注解或类名会有错误提示的;
  • 注意assertions(消息参数移到最后),timeoutexpected exceptions,具体见上文说明;
  • 更新assumptions;
  • 用注解@ExtendWith替换 @RunWith@Rule@ClassRule,以及解决替换后的问题;

Note that migrating parameterized tests will require a little more refactoring, especially if you have been using JUnit 4 Parameterized (the format of JUnit 5 parameterized tests is much closer to JUnitParams).

新特性

上文说明的是已有的特性功能及变动,JUnit5还提供了不少新的特性,让测试用例的更具有描述性和维护性。

定义描述信息

可以在类或方法中使用注解@DisplayName定义描述信息,使得描述测试的目的和追踪失败更容易,如:

@DisplayName("Test MyClass")
class MyClassTest {
    @Test
    @DisplayName("Verify MyClass.myMethod returns true")
    void testMyMethod() throws Exception {    
        // ...
    }
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(
    status = Status.STABLE,
    since = "5.0"
)
public @interface DisplayName {
    String value();
}

更多信息:See the JUnit document for specifics and examples.

断言

JUnit5提供了新的断言,如下:

  • assertIterableEquals() 对Iterable<?>遍历equals(),如果元素是Iterable递归继续;
  • assertLinesMatch() 对字符列表(list,stream)匹配;
  • assertAll() 所有断言一起执行,失败的不影响其他执行;
  • assertThrows() 和assertDoesNotThrow() 替代注解@Test里的expected property

@Nested

允许你有一个内部类,它本质上是一个测试类,允许你在下面组合几个测试类相同的父级(具有相同的初始化,如:

@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());
        }
    }
}

使用displayname在测试报告中描述测试的目的以及结构关系

@ParameterizedTest

JUnit4中已经存在,内置的如JUnit4Parameterized或者第三方的JUnitParams。而在JUnit5中,借鉴两者好的特性以此完全内置了。如下:

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

形式看起来类似JUnitParams,参数直接传递给测试方法。需要注意测试的值可以来自多个不同的源。例子中只用了一个参数,所以@ValueSource很方便。还有@EmptySource空字符,@NullSource空值,其他的还有如@EnumSource@ArgumentsSource,多参数的如@MethodSource@CsvSource

另一个添加的测试类型是@RepeatedTest,一个测试重复执行指定次数。

条件执行

JUnit5提供了ExecutionCondition扩展api来有条件的启用或停用一个测试或类。如同在测试上使用注解@Disabled一样,这里提供了判断条件。内置的条件如下:

  • @EnabledOnOs 和@DisabledOnOs: 指定的操作系统
  • @EnabledOnJre and @DisabledOnJre: 指定的jre版本
  • @EnabledIfSystemProperty: 如果满足JVM系统属性值启用
  • @EnabledIf: 如果If条件满足启用

测试模板

测试模板不是常规测试;它们定义了一组要执行的步骤,然后可以在其他地方使用特定的调用上下文执行。 For details and examples, see the documentation.

动态测试

动态测试就像测试模板,要运行的测试是在运行时生成的。然而,测试模板是用一组特定的步骤来定义并运行多次,而动态测试使用相同的调用上下文,但可以执行不同的逻辑。动态测试的一个用途是将一个抽象对象的列表流化,并根据它们的具体类型为每个对象执行一组单独的断言。There are good examples in the documentation.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值