JUnit 5 快速上手(从 JUnit 4 到 JUnit 5)

一直在关注 JUnit 5 的演进,自两年前首个 ALPHA 版后,经历了 6 的 Milestone, 3 个 RC 终于在 2017/09/10 正式发布了。其实还从未对其深究过,今天算是正式开始体验。

不像以往的版本,JUnit 5 现在是三个模块的合体 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  1. JUnit Platform: 运行测试的基础平台。还定义了开发测试框架的 TestEngine API。并提供了命令行执行测试以及与 Gradle, Maven, JUnit4 Runner 的集成
  2. JUnit Jupiter: 包含了新的编程和扩展模型。它还提供了一个运行新型测试的 TestEngine 实现
  3. JUnit Vintage: 提供了一个让 JUnit Platform 运行 JUnit 3 和 JUnit 4 的 TestEngine 实现

以上三个模块分工还是很明确,因此

  1. 从现有的 JUnit 4 项目步入到 JUnit 5 至少两 JUnit Platform 和  JUnit Vintage 两个
  2. 建立全新项目可以只引入  JUnit Platform 和 JUnit Jupiter
  3. 混合型当然是三个全部引入

但是由于 jar 包之间本身存在某种依赖关系,所以实际上 pom.xml 可以比想像的更简单

JUnit 5 要求 Java 8 及以上的版本,甚至与 Java 9 也有所展望,这个对于怕出乱子的领导是很难的。

关于 Maven 中如何使用 JUnit 5,可以参考 junit5-maven-consumer 这个 pom.xml 配置; 还可以酌情对该配置进行裁剪。

举例说明:

  1. 全新项目,只有 JUnit 5 新型编程/扩展模型,pom.xml 中只需要依赖
     
    1
    2
    3
    4
    5
    6
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.0.0</version>
        <scope>test</scope>
    </dependency>
  2. 只需让原来的 JUnit 4 在 JUnit 5 平台上运行的话,pom.xml 中只需要依赖
     
    1
    2
    3
    4
    5
    6
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>4.12.0</version>
        <scope>test</scope>
    </dependency>

    junit-vintage-engine 会自动引入相应版本的 JUnit。当然,如果只是这种需求话,真是吃饱了撑着

  3. 从 JUnit 4 迁移到 JUnit 5, 这是最贴合实际的需求,此时 pom.xml 只的依赖就是同时需要以上两个

依赖配置好了,在目前最新的 IntelliJ IDEA 2017.2.4 中可以正常同时执行 JUnit 4 和 JUnit 5 的测试用例了。但目前为止 mvn test 命令只会测试 JUnit 4 的测试用例,若要让 Maven 识别出所有的测试用例还得加上一个构建插件配置

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.19.1</version>
            <configuration>
                <includes>
                    <include>**/Test*.java</include>
                    <include>**/*Test.java</include>
                </includes>
                <properties>
                    <!-- <includeTags>fast</includeTags> -->
                    <excludeTags>slow</excludeTags>
                    <!--
                    <configurationParameters>
                        junit.jupiter.conditions.deactivate = *
                    </configurationParameters>
                    -->
                </properties>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.0.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

关键是 junit-platform-surefire-provider 的配置,其他部分只需注意 JUnit 5 可用  @Tag 来标识,包含或排除测试用例。现在 Maven 上也没问题了。

现在可以来个兼具 JUnit 4 和 JUnit 5 具体来个例子,pom.xml 中需要加上前面提到了三块配置。下面开始列出代码

待测试类 Calculation

 
1
2
3
4
5
6
7
8
package cc.unmi;
 
public class Calculation {
 
    public int add(int first, int second) {
        return first + second;
    }
}

JUnit 4 测试类

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cc.unmi;
 
import org.junit.Test;
 
import static org.junit.Assert.assertEquals;
 
public class CalculationTest {
 
    @Test
    public void onePlugTwoShouldBeThree() {
        Calculation calc = new Calculation();
        assertEquals(3, calc.add(1, 2));
    }
}

JUnit 5 测试类

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cc.unmi;
 
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
 
public class CalculationJunit5Test {
 
    @Test
    @DisplayName("1 plus 2 should be 3")
    public void onePlugTwoShouldBeThree() {
        Calculation calc = new Calculation();
        assertEquals(3, calc.add(1, 2));
    }
 
    @Test
    @Tag("slow")
    public void slow(){
 
    }
}
 

注意:是 JUnit 4 测试类还是 JUnit 5 测试类,关键看注解 @Test 是来自于哪个包,比如说

  1. @Test 是 org.junit.Test,那么它是老的 JUnit 4 的测试类(也可能是 JUnit 3 的)
  2. @Test 是 org.junit.jpiter.api.Test, 那么它是 JUnit 5 的测试类

@Test 注解的出处会影响其他标签的行为,例如用 @org.junit.Test 搭配 JUnit 5 特有注解(像 @DisplayName) 就是来到捣乱的,那么这时候 @DisplayName 不会有任何效果。但 JUnit 4  的 @Test 与新的断言方法是可以工作的。

如果我们不得不在一个项目中混合 JUnit 4 和  JUnit 5 的话,我们必须保持使用的  API 是版本一致的。

现在我们可以开始运行测试用例了,分别是在 IDE 和控制台下

Intellij IDEA 2017.2.4

JUnit 5 快速上手(从 JUnit 4 到 JUnit 5)

IDEA 可以正确处理 @DisplayName 标签,但是对 @Tag 视而不见

Maven 控制台

JUnit 5 快速上手(从 JUnit 4 到 JUnit 5)

Maven 能够根据 @Tag 进行排除用例,但是它在一切正常时原本就不会显示测试用类名,所以也就不知道 @DisplayName 是什么,除非自定义 RunListener.

那么 Maven 在有用例失败时,以往都是显示失败的方法名称,那么 JUnit 5 是怎么显示失败的测试用例呢?还是照旧

JUnit 5 快速上手(从 JUnit 4 到 JUnit 5)

或许 JUnit 5 还是认为显示失败的实际方法名更有助于定位错误。因此,基本上认为 @DisplayName 是给 IDE 用的。要不就自定义 RunListener 吧。

本来想在此了解一下 JUnit 5 的新特性,思考之后还是觉得目前最紧要的可能是如何从 JUnit 4 迁移到  JUnit 5, 所以才有了上面的内容。我们完全可以先让两个版本的 JUnit 测试用例并行,然后逐步替换掉  JUnit 4 测试用例。官方的迁移文档是 Migrating from JUnit 4. 迁移不光是 API 的替换,还有 JUnit 5 不再支持 Rule 了,还要验证 JUnit 5 是否能与 Mock 框架 Mockito, JMockit 等正常工作。