相信很多软件开发对于单元测试和Junit都不会感到陌生。JUnit 是由两位编程大师Kent Back 和 Erich Gamma 在1997年编写的Java开源单元测试框架,它通过大量的注解(Annotation)和约定(Convention) 运行和管理单元测试用例。
JUnit 的作者 Kent Back 曾经说过,软件开发如果没有单元测试就像人走在钢丝上,没有任何的保障。
Junit 5 是Junit框架的一次重大升级,充分利用了Java 8以及后续版本Java语言中大量新的特性,提升单元测试的编写效率和运行效率。在新框架中使用函数式和声明式编程风格在新框架中更容易编单元测试使用以及更具可读性。同时需要注意JUnit 5 需要Java 8 或者更高的版本运行,但支持低版本编译后的被测试的代码,但可能不是那么友好,强烈建议低于Java 8 的应用尽快迁移到Java 8 或者更高版本.
跟之前JUnit版本不同,JUnit 5 由几个不同模块组成,这几个模块同时也是JUnit 5的子项目。
JUnit Platform 主要负责在JVM上运行测试框架,它在JUnit和它的调用者之间定义了稳定且强大的接口,例如构建工具。JUnit Platform很容易让调用者集成JUnit用于发现或者执行单元测试用例。同时它还定义了TestEngine API 用于开发自定义测试构架同时运行在JUnit Platform上面。通过自定义实现TestEgnine API, 我们可以插入第三方测试组件到JUnit里面。
JUnit Jupiter 组件包含了用于编写JUnit 5 测试用例的新编程和扩展模型。相对于JUnit 4, JUnit 5提供了以下新的注解:
JUnit Vintage 主要为了支持JUnit 3/4的单元测试用例在JUnit 5上面运行,对于全新的项目或者JUnit 5 测试则无需引入。
org.junit.jupiter
junit-jupiter
RELEASE
test
@BeforeAll
static void setup() {
log.info("@BeforeAll -在此测试类里所有测试方法执行前执行一次");
}
@BeforeEach
void init() {
log.info("@BeforeEach - 在此测试类里每一个测试方法执行前执行一次");
}
@DisplayName("正向测试用例1")
@Test
void testSingleSuccessTest() {
log.info("Success");
}
@DisplayName("负向测试用例1")
@Test
void testSingleFailedTest() {
log.info("failed");
}
@Test
@Disabled("Not implemented yet")
void testShowSomething() {
}
@AfterEach
void tearDown() {
log.info("@AfterEach - 在此测试类里每一个测试方法执行后执行一次");
}
@AfterAll
static void done() {
log.info("@AfterAll - 在此测试类里所有测试方法执行后执行一次");
}
JUnit 5 从Java 8 引入大量的特性以提高测试编写效率,特别是lambda表达式。Assertions 在JUnit 5 中已经全部移至org.junit.jupiter.api目录下,所有的断言方法均为静态方法。使用lambda表达式其中一个作用是并行处理构造用例输出以节约时间和资源。
@DisplayName("使用lambda表达式构建测试用例1")
@Test
void lambdaExpressions() {
List numbers = Arrays.asList(1, 2, 3);
assertTrue(numbers.stream()
.mapToInt(Integer::intValue)
.sum() == 6, () -> "List中数值之和等于6");
}
@DisplayName("使用assertAll将多个断言组合在一起")
@Test
void groupAssertions() {
int[] numbers = {0, 1, 2, 3, 4};
assertAll("numbers",
() -> assertEquals(numbers[0], 1),
() -> assertEquals(numbers[3], 3),
() -> assertEquals(numbers[4], 1)
);
}
Assumptions 在JUnit 5 中也全部移至org.junit.jupiter.api目录下,所有的断言方法均为静态方法。Assumptions 主要用于进入测试前的条件判断,一般用于判断外部条件是否满足,比如说单元测试的运行环境是否能满足单元测试运行。
@DisplayName("使用Assumption构建测试用例正常执行")
@Test
public void trueAssumption() {
assumeTrue(System.getProperty("os.name").contains("Windows"));
assertEquals(5 + 2, 7);
}
@DisplayName("使用Assumption构建测试用例不符合假设跳过执行")
@Test
public void trueAssumption() {
assumeTrue(System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux"));
assertEquals(5 + 2, 7);
}
在JUnit 5中可以用assertThrows() 方法进行异常测试。
@DisplayName("异常测试1:通过异常信息验证测试结果")
@Test
void shouldThrowException() {
Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
throw new UnsupportedOperationException("Not supported");
});
assertEquals("Not supported", exception.getMessage());
}
@DisplayName("异常测试2:通过异常类型验证测试结果")
@Test
void assertThrowsException() {
String str = null;
assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
});
}
自定义测试集合在junit-platform模块,创建新的测试集合需要引入 junit-platform-suite-engine依赖,示例如下:
org.junit.platform
junit-platform-suite-engine
RELEASE
test
@Suite
@SelectPackages("me.wangyun.junit5")
@ExcludePackages("me.wangyun.suites")
@SuiteDisplayName("Junit 5 自定义测试集合")
public class AllUnitTestPackageSuites {
}
执行结果和日志如下图:
除了以上Pakcage级别的筛选,JUnit 5 还提供了以下Tag、Class级别的注解来将测试用例选择或者排除进入测试集合。
DynamicTest 是JUnit 5 的新特性。标准的测试用例在编写时用@Test注解并在编译期间指定,而动态测试用例是在运行期生成,这些测试用例由注解@TestFactory 的测试方法生成, 测试工厂方法不能为静态或者私有。动态测试用例不同于标准的测试用例,不支持标准测试用例的生命周期和回调,比如说@BeforeEach 和 @AfterEach 方法。
@TestFactory
@DisplayName("测试创建动态测试集合1")
Collection dynamicTestsWithCollection() {
return Arrays.asList(
DynamicTest.dynamicTest("加法测试",
() -> assertEquals(2, Math.addExact(1, 1))),
DynamicTest.dynamicTest("乘法测试",
() -> assertEquals(4, Math.multiplyExact(2, 2))));
}
执行结果和日志如下图:
JUnit 5 动态测试用例工厂除了Collection类型,还支持Iterable、Iterator、Stream等类型。动态测试用例可视为@ParameterizedTest的补充,@ParameterizedTest支持标准测试的完整生命周期(@BeforeEach 和 @AfterEach)。
尽管JUnit 5 Jupiter 编程和扩展模型不再支持JUnit 4的Rules和Runners特性,为了能让存量测试用例仍然能够在JUnit 5 平台上运行, JUnit 5提供了JUnit Vintage测试引擎运行JUnit 3和JUnit 4的测试用例。对于存量的单元测试用例迁移到JUnit 5有以下建议:
JUnit 5 在架构上将单元测试运行平台和API进行解藕,将运行支持和用例编写的依赖分离。运行平台可以方便的集成IDE, 构建工具或者其它单元测试框架,让JUnit生态更加开放,更多的接口开放出交给用户自定义。试完一轮后,虽然目前没有发现一个特别强烈的理由将现有的JUnit 4的测试框架迁移到JUnit 5,但也看到新的框架更加开放,更灵活,运行效率更高。
微胖中年男码农隔壁老王,勿念。