1. JUnit 和 JUnit 5

相信很多软件开发对于单元测试和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 或者更高版本.

2. JUnit 5 架构

跟之前JUnit版本不同,JUnit 5 由几个不同模块组成,这几个模块同时也是JUnit 5的子项目。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

2.1 JUnit Platform

JUnit Platform 主要负责在JVM上运行测试框架,它在JUnit和它的调用者之间定义了稳定且强大的接口,例如构建工具。JUnit Platform很容易让调用者集成JUnit用于发现或者执行单元测试用例。同时它还定义了TestEngine API 用于开发自定义测试构架同时运行在JUnit Platform上面。通过自定义实现TestEgnine API, 我们可以插入第三方测试组件到JUnit里面。

2.2 JUnit Jupiter

JUnit Jupiter 组件包含了用于编写JUnit 5 测试用例的新编程和扩展模型。相对于JUnit 4, JUnit 5提供了以下新的注解:

  • @TestFactory – 表示这是一个用于动态测试用例的测试工厂方法
  • @DisplayName – 为测试类或者测试方法定义一个显示名字让团队易于理解
  • @Nested – 表示这个类是嵌套的,非静态测试类
  • @Tag – 定义标签以便用例执行时过滤测试用例
  • @ExtendWith – 注册自定义扩展
  • @BeforeEach – 表示这个被注解方法在每一个测试用例执行前都会执行一次(同之前的 @Before),可用于初始化测试数据
  • @AfterEach – 表示这个被注解方法在每一个测试用例执行后都会执行一次(同之前的 @After),可用于清理上一次执行结果
  • @BeforeAll – 表示这个被注解方法在所有测试用例执行前都会执行一次(同之前的 @BeforeClass),相当于整个测试类的初始化。
  • @AfterAll – 表示这个被注解方法在所有测试用例执行后都会执行一次(同之前的@AfterClass),相当于整个测试类回收或者清理。
  • @Disable – 禁用一个测试类或者方法 (previously @Ignore)

2.3 JUnit Vintage

JUnit Vintage 主要为了支持JUnit 3/4的单元测试用例在JUnit 5上面运行,对于全新的项目或者JUnit 5 测试则无需引入。

3. JUnit 5 示例

3.1 JUnit 5 Maven 依赖

  • 将Junit 5 组件加入到pom.xml , 此处引入最新的版本。另外JUnit 5 Platform提供丰富的组件跟不同的工具平台集成,可按需引入。比如说junit-platform-console、junit-platform-reporting、junit-platform-launcher。
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>RELEASE</version><scope>test</scope></dependency>

3.2 基础注解 (Basic Annotations)

  • @BeforeAll,@BeforeEach,@DisplayName,@Disabled,@AfterEach,@AfterAll
@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 - 在此测试类里所有测试方法执行后执行一次");
}
注意@BeforeAll和@AfterAll 需要定义为静态方法,否则无法通过编译。

执行结果和日志如下图:

3.3 断言(Assertions)

JUnit 5 从Java 8 引入大量的特性以提高测试编写效率,特别是lambda表达式。Assertions 在JUnit 5 中已经全部移至org.junit.jupiter.api目录下,所有的断言方法均为静态方法。使用lambda表达式其中一个作用是并行处理构造用例输出以节约时间和资源。

  • 示例1: 使用lambda表达式构建测试用例
@DisplayName("使用lambda表达式构建测试用例1")
@Test
void lambdaExpressions() {List<Integer> numbers = Arrays.asList(1, 2, 3);assertTrue(numbers.stream().mapToInt(Integer::intValue).sum() == 6, () -> "List中数值之和等于6");
}

执行结果和日志如下图:

  • 示例2: 使用assertAll构建测试用例断言组合,若断言组合中任何一个断言失败将抛出MultipleFailuresError。
@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));
}

执行结果和日志如下图:

3.4 假设(Assumptions)

Assumptions 在JUnit 5 中也全部移至org.junit.jupiter.api目录下,所有的断言方法均为静态方法。Assumptions 主要用于进入测试前的条件判断,一般用于判断外部条件是否满足,比如说单元测试的运行环境是否能满足单元测试运行。

  • 示例1: 检查当前操作系统为 Windows 要后继续执行
@DisplayName("使用Assumption构建测试用例正常执行")
@Test
public void trueAssumption() {assumeTrue(System.getProperty("os.name").contains("Windows"));assertEquals(5 + 2, 7);
}

执行结果和日志如下图:

  • 示例2: 检查当前操作系统为Linux要后继续执行, 若是非Linux则跳过执行并抛出TestAbortedException异常。
@DisplayName("使用Assumption构建测试用例不符合假设跳过执行")
@Test
public void trueAssumption() {assumeTrue(System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux"));assertEquals(5 + 2, 7);
}

执行结果和日志如下图:

3.5 异常测试(Exception Testing)

在JUnit 5中可以用assertThrows() 方法进行异常测试。

  • 示例1:通过异常信息来验证测试结果
@DisplayName("异常测试1:通过异常信息验证测试结果")
@Test
void shouldThrowException() {Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {throw new UnsupportedOperationException("Not supported");});assertEquals("Not supported", exception.getMessage());
}

执行结果和日志如下图:

  • 示例2:通过异常类型来验证测试结果
@DisplayName("异常测试2:通过异常类型验证测试结果")
@Test
void assertThrowsException() {String str = null;assertThrows(IllegalArgumentException.class, () -> {Integer.valueOf(str);});
}

执行结果和日志如下图:

3.6 测试集合(Test Suites)

自定义测试集合在junit-platform模块,创建新的测试集合需要引入 junit-platform-suite-engine依赖,示例如下:

<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite-engine</artifactId><version>RELEASE</version><scope>test</scope>
</dependency>
  • 示例1:一个简单的自定义测试集合
@Suite
@SelectPackages("me.wangyun.junit5")
@ExcludePackages("me.wangyun.suites")
@SuiteDisplayName("Junit 5 自定义测试集合")
public class AllUnitTestPackageSuites {
}

执行结果和日志如下图:

除了以上Pakcage级别的筛选,JUnit 5 还提供了以下Tag、Class级别的注解来将测试用例选择或者排除进入测试集合。

  • @SelectClasses
  • @SelectPackages
  • @IncludePackages
  • @ExcludePackages
  • @IncludeClassNamePatterns
  • @ExcludeClassNamePatterns
  • @IncludeTags
  • @ExcludeTags

3.6 动态测试用例(DynamicTest)

DynamicTest 是JUnit 5 的新特性。标准的测试用例在编写时用@Test注解并在编译期间指定,而动态测试用例是在运行期生成,这些测试用例由注解@TestFactory 的测试方法生成, 测试工厂方法不能为静态或者私有。动态测试用例不同于标准的测试用例,不支持标准测试用例的生命周期和回调,比如说@BeforeEach 和 @AfterEach 方法。

  • 示例一: 创建一个动态测试
@TestFactory
@DisplayName("测试创建动态测试集合1")
Collection<DynamicTest> 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)。

4. 如何迁移JUnit 4 到 JUnit 5

尽管JUnit 5 Jupiter 编程和扩展模型不再支持JUnit 4的Rules和Runners特性,为了能让存量测试用例仍然能够在JUnit 5 平台上运行, JUnit 5提供了JUnit Vintage测试引擎运行JUnit 3和JUnit 4的测试用例。对于存量的单元测试用例迁移到JUnit 5有以下建议:

  • Annotations 迁移至 org.junit.jupiter.api 包。
  • Assertions 迁移至 org.junit.jupiter.api.Assertions。
  • Assumptions 迁移至 org.junit.jupiter.api.Assumptions。
  • 移除@Before 和 @After; 使用 @BeforeEach 和 @AfterEach 代替。
  • 移除@BeforeClass 和 @AfterClass; 使用 @BeforeAll 和 @AfterAll 代替。
  • 移除@Ignore; 使用 @Disabled 或者其它内置执行条件代替。
  • 移除@Category; 使用 @Tag 代替。
  • 移除@RunWith; 被@ExtendWith取代。
  • 移除@Rule 和 @ClassRule; 被 @ExtendWith and @RegisterExtension取代。
  • JUnit 5 Jupiter 中的断言和假设接受失败消息作为最后一个参数,而不是第一个参数。接受失败消息作为最后一个参数,而不是第一个参数。

5. 结论

JUnit 5 在架构上将单元测试运行平台和API进行解藕,将运行支持和用例编写的依赖分离。运行平台可以方便的集成IDE, 构建工具或者其它单元测试框架,让JUnit生态更加开放,更多的接口开放出交给用户自定义。试完一轮后,虽然目前没有发现一个特别强烈的理由将现有的JUnit 4的测试框架迁移到JUnit 5,但也看到新的框架更加开放,更灵活,运行效率更高。

6. 关于作者

微胖中年男码农隔壁老王,勿念。

JUnit 5 初探相关推荐

  1. hadoop入门程序:词频统计

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_42437577/article/ ...

  2. Maven初探-如何快速入手一个项目

    这已是一个工具的时代,而不是一个到处搜集JAR的年代,如果还在为搭建一个项目而到处找依赖,那你就彻底OUT了.下面,跟各位一起重温学习下Maven. 什么是Maven Maven项目对象模型(POM) ...

  3. Hibernate初探

    Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行.程序员往往只需定义好了POJO 到数据 ...

  4. JVM初探——使用堆外内存减少Full GC

    问题: 大部分主流互联网企业线上Server JVM选用了CMS收集器(如Taobao.LinkedIn.Vdian), 虽然CMS可与用户线程并发GC以降低STW时间, 但它也并非十分完美, 尤其是 ...

  5. gRPC编码初探(java)

    背景:gRPC是一个高性能.通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众 ...

  6. Android自动化测试初探

    本文内容:Android自动化测试框架初探 版权声明:本文为原创文章,未经允许不得转载 博客地址:http://blog.csdn.net/kevindgk 初步了解 什么是自动化测试 优点 缺点 前 ...

  7. SpringBoot初探

    文章目录 SpringBoot初探 一.SpringBoot介绍 回顾什么是Spring Spring框架是如何简化开发的 什么是SpringBoot SpringBoot应用的创建 Hello Wo ...

  8. JVM初探- 使用堆外内存减少Full GC

    JVM初探-使用堆外内存减少Full GC 标签 : JVM 问题: 大部分主流互联网企业线上Server JVM选用了CMS收集器(如Taobao.LinkedIn.Vdian), 虽然CMS可与用 ...

  9. 2021年大数据Flink(九):Flink原理初探

    Flink原理初探 Flink角色分工 在实际生产中,Flink 都是以集群在运行,在运行的过程中包含了两类进程. JobManager: 它扮演的是集群管理者的角色,负责调度任务.协调 checkp ...

最新文章

  1. 腾讯云 cloudbase 云开发使用笔记
  2. 《高性能JavaScript》(读书笔记)
  3. Java 正则表达式使用详解
  4. 使用LogKit进行日志操作
  5. mysql读写分离有用吗_MySQL的使用中实现读写分离的教程
  6. Model init when entering configuration tab in workbench
  7. Vue-cli proxyTable 解决开发环境的跨域问题(转)
  8. WordPress根目录(Root)
  9. linux常用命令小结
  10. 苹果笔记本电脑如何清理缓存内存空间?
  11. leetcode 买卖股票的最佳时机含手续费(Java)
  12. 开源项目—FLutter/iOS/Android编写的三个计算器APP
  13. Unity相机设置CullingMask
  14. warnings 忽视警告
  15. FPS透视原理(完)
  16. c语言背包问题装字母,C语言动态规划之背包问题详解
  17. [原创] photoshop cs3视频教程全程高清版
  18. java车辆管理系统开发源码_JSP写的车辆管理系统 - WEB源码|JSP源码/Java|源代码 - 源码中国...
  19. Java项目:大学生健康档案管理系统(java+jpa+swagger-ui+springboot+vue+mysql)
  20. springboot国际化配置中英文切换

热门文章

  1. python不弹窗执行js_python使用execjs执行js
  2. 阿里云DataWorks学习——数仓架构设计
  3. vulnhub DC系列 DC-4
  4. Budibase安装
  5. 电感线圈受什么因素音响
  6. 我的电脑锁定计算机怎么没有,笔记本电脑屏幕锁住了按键盘没有反应 笔记本电脑屏幕锁住了按键盘没有反应怎么办...
  7. 大学中的matlab课程,Matlab及在电子信息课程中的应用-中国大学mooc-题库零氪
  8. Linux下select函数实现的聊天服务器
  9. Spring Cloud ---02-- Eureka注册中心
  10. 三维曲面的绘制(Python/MATLAB)