对于所有的 Java 开发人员来说,你可以没有听说过 Spring 或是 Hibernate 框架,但是一定听说过 JUnit。JUnit 作为 Java 单元测试的鼻祖与事实上的标准,在非常多的项目中被使用。即便新兴的单元测试框架,如 TestNG 等,不断出现,JUnit 的重要性仍然是不言而喻的。目前广泛使用的是 JUnit 4 版本,而 JUnit 即将迎来它的新版本 JUnit 5。JUnit 5 在增加了很多的新特性的同时,又保持了对 JUnit 4 的向后兼容性。本文对 JUnit 5 进行了详细的介绍。
JUnit 5 简介
与之前的版本不同,JUnit 5 由三个不同的模块组成。第一个模块是 JUnit 平台,其主要作用是在 JVM 上启动测试框架。它定义了一个抽象的 TestEngine API 来定义运行在平台上的测试框架,同时还支持通过命令行、Gradle 和 Maven 来运行平台。第二个模块是 JUnit Jupiter,包含了 JUnit 5 新的编程模型和扩展机制。第三个模块是 JUnit Vintage,允许在平台上运行 JUnit 3 和 JUnit 4 的测试用例。
JUnit 5 对 Java 运行环境的低要求是 Java 8。可以在 Eclipse 和 IntelliJ IDEA 上运行 JUnit 5 测试。本文的示例基于 IntelliJ IDEA 上开发,并使用 Gradle 作为构建工具。不过目前 IDE 对 JUnit 5 的支持还比较有限,只有新版本的 IntelliJ IDEA 原生支持,在其它 IDE 上需要使用命令行工具来运行。
编写测试用例
JUnit 5 对编写单元测试用例的方式做了一系列的改进,如下介绍。
JUnit 5 注解
JUnit 5 提供了一些常用的注解在编写测试用例的时候使用。其中的一些注解和 JUnit 4 的注解有相同的名称,不过所在的 Java 包变成了 org.junit.jupiter.api。常用的注解见表 1。
表 1. JUnit 5 常用注解
清单 1 中给出了使用这些注解编写的单元测试用例。
清单 1. 使用常用注解的单元测试用例
@DisplayName("Calculator")
public class CalculatorTest {
private Calculator calculator;
@BeforeAll
public static void init() {
System.out.println("Start testing");
}
@BeforeEach
public void create() {
this.calculator = new Calculator();
}
@AfterEach
public void destroy() {
this.calculator = null;
}
@AfterAll
public static void cleanup() {
System.out.println("Finish testing");
}
@Test
@DisplayName("Test 1 + 2 = 3")
public void testAdd() {
assertEquals(3, this.calculator.add(1, 2));
}
@Test
@DisplayName("Test 3 - 2 = 1")
public void testSubtract() {
assertEquals(1, this.calculator.subtract(3, 2));
}
@Disabled
@Test
@DisplayName("disabled test")
public void ignoredTest() {
System.out.println("This test is disabled");
}
}
在这些注解中,实用的应该是@DisplayName。通过@DisplayName,开发人员可以为每个测试用例添加更具体的名字,更容易传达用例所要测试的内容。
通过@Tag 注解可以为测试类或方法添加标签,但是不同的标签只是通过字符串来进行区分,并不是类型安全的。一个拼写错误可能造成标签没有被正确应用。更好的做法是使用类型安全的元注解(meta annotation)。编译器会对元注解标签的正确性进行验证,从而减少无意的错误。清单 2 中定义了元注解标签@Remote,对应于标签 remote。
清单 2. 元注解标签
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("remote")
public @interface Remote {
}
清单 3 中展示了@Remote 的用法。使用@Remote 的作用等同于@Tag("Remote"),为 testGetUser 方法添加了标签 remote。使用@Remote 不仅提高了代码的可读性,也可以避免无意的拼写错误带来的问题。
清单 3. 使用元注解标签
@DisplayName("Remote test")
public class RemoteTest {
@Test
@Remote
public void testGetUser() {
System.out.println("Get user");
}
}
JUnit 5 断言
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
第一类是简单断言,用来对单个值进行简单的验证,常用的方法见表 2。