背景
最近开始使用 JUnit5 写单元测试,本地运行成功之后提交代码,触发流水线进进行覆盖率计算。结果出来之后傻眼了,几百个单侧只能识别到 2 个。
先简单说一下具体的环境。
本地使用 IDEA 自带的 maven,版本为 3.9.6,JUnit 版本 5.7.0。流水线使用 jenkins 触发 maven 命令,用的 maven 版本是 3.8.1。
本地 idea maven 插件直接 test 运行,可以识别到单测数量:362
jenkins 日志上看到的,只有 2 个
排查思路
由于本地运行是可以的,但 jenkins 上只能看到 2 个 case,所以第一个时间观察了被识别的单测与未识别的单测的区别,这一看才发现真有区别:
不识别的 case 都是 package-private access,为什么会是 package-private 呢,是因为 JUnit5 的改变导致的:
we (the JUnit 5 team) believe in the principle "Less is more", meaning the less you have to type to achieve your goal, the better!
我们(JUnit 5 团队)相信“少即是多”的原则,这意味着您为实现目标而需要输入的内容越少越好!
既然是 access 导致的无法识别,那就修改为 public 好了。修改后继续 jenkins 运行,报错 npe,所有 mock 的对象都是空了。
到了这一步,基本上可以认定是环境问题了。
于是通过查看 jenkins 的构建参数,发现使用的 maven 版本为 3.8.1,从官网下载好 3.8.1 之后,使用 3.8.1 mvn 运行发现报了相同的错。
如此一来,可以判断出是因为 3.9.6 版本比 3.8.1 的版本多了对 JUnit5 的适配,但到底是哪些适配呢?
通过查看 JUnit5 的官方文档,找到这部分描述:
Starting with version 2.22.0, Maven Surefire and Maven Failsafe provide native support for executing tests on the JUnit Platform. The pom.xml file in the junit5-jupiter-starter-maven project demonstrates how to use the Maven Surefire plugin and can serve as a starting point for configuring your Maven build.
从版本 2.22.0开始,Maven Surefire 和 Maven Failsafe 为在 JUnit 平台上执行测试提供本机支持。 junit5-jupiter-starter-maven项目中的pom.xml文件演示了如何使用 Maven Surefire 插件,并且可以作为配置 Maven 构建的起点。
In order to have Maven Surefire or Maven Failsafe run any tests at all, at least one TestEngine implementation must be added to the test classpath.
为了让 Maven Surefire 或 Maven Failsafe 运行任何测试,必须至少将一个TestEngine实现添加到测试类路径中。
按照这个说法,添加一个 engine 依赖和指定 surefire plugin 版本:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- JUnit 5 requires Surefire version 2.22.0 or higher -->
<version>2.22.0</version>
</plugin>
再使用 3.8.1 mvn 执行 test,就可以正常识别到单测了。
原因
想必看到这里,肯定很好奇为什么 3.9.6 版本的 mvn 就可以不用指定 surefire 和 engine?
是不是因为 3.9.6 已经内置了?
答案是肯定的。
在 2.22.0 版本的 surefire 当中,JUnit 团队做了 JUnit5 的 native 支持:https://issues.apache.org/jira/browse/SUREFIRE-1330
而 3.8.1 的 surefire 版本为 2.12.4,属于老版本。
在 3.9.0 的 release note 中,有这么一个更新:
[MNG-7666] - Update default binding and lifecycle plugin versions
通过查看这个改进,发现了 surefire 的版本更新:
总结
至此,为什么 3.9.0 可以原生支持 JUnit5 的原因就找到了,就是因为 surefire 版本做了更新,而 3.8.1 的版本比价老还不支持,需要手动指定版本。
到这里,其实具体的解决思路也很简单了,直接将 surefire 的版本手动替换为 3.0.0-M8 即可