Apache Maven Surefire

Apache Maven Surefire 本身是一个测试框架。

Maven Surefire Plugin 和 Maven Failsafe Plugin 都是这个项目的模块。

Surefire 插件

Surefire 是在 maven 的构建生命周期里面,test phase 执行单元测试的插件。
Surefire 的意思是“完全,一定成功的”。任何单元测试失败,都会导致构建失败。
Surefire 跑测试失败,会在现场留下名如hs_err*的文件。

用法

这个插件只有一个 goal,就是 test。

因此,使用它都不需要配置什么 configuration 和 phase。

1
2
3
4
5
6
7
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
</plugins>

它默认就会在 test phase 被执行:

1
mvn test

不管底层的 test provider 是 JUnit 还是 TestNG,甚至只是一个 pojo,它都可以运行测试。

对于pojo,插件可以自动运行类里面的 public testxxx 方法,也可以使用 JDK 1.4 时代以后使用的断言。

对于 JUnit 之类的 test provider,可以使用以下配置进行并行测试:

1
2
3
4
5
6
7
8
9
10
11
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
</plugin>
</plugins>

类路径太长问题

https://maven.apache.org/surefire/maven-surefire-plugin/examples/class-loading.html

整体而言,就是有些 OS 的命令行不允许太长的类路径。

解决这个问题的思路是,不在命令行内传递类路径,而使用进程内部的参数传递机制。由此诞生的解决方案有:

  1. 单独的类加载器。这样可以通过执行一个伪的 booter 程序来绕开类路径的直接限制,在程序内部再 load 我们的目标程序。这个方法也有缺点。首先,java.class.path 这个系统变量不会包括启动 jar。如果应用程序需要关心这个点,可能会导致问题。其次,程序内部可能会调用Classloader.getSystemClassLoader()而不是使用缺省的类加载器(也就是我们这个 isolated 类加载器)来加载特定的类,这会导致?问题(日后专文讨论)。

  2. 使用一个 Manifest-Only 的 Jar。这个 Jar 几乎为空,只有一个MANIFEST.MF 文件。JVM 会把这个清单文件里的类路径 honor 起来当做 directive。比如我们可以用一个单独的 Class-Path 的 attribute 来填写我们的很长的类路径。

Failsafe 插件

Failsafe 是在 maven 的构建生命周期里面,test phase 执行集成测试的插件。
Failsafe 看起来是 Surefire 的同义词,但事实上它使用更安全的方式运作。集成测试的失败,和构建过程解耦了,构建不会因此失败。

JaCoCo

JaCoCo 本质上是是一个 java agent,基于 Java 的 Instrumentation API。可以对类文件进行 on-the-fly 的 instrumentation。它是在 class loading 的阶段通过 in memory 字节码增强的形式,进行类型补强。

JaCoCo 本身支持三种数据采集模式:

  • 写入文件系统
  • 作为一个 server 让其他客户端采集
  • 作为一个 client 去连接某个 TCP 端点获取数据

JMX 的接口本身是没有鉴权和授权机制的,所以使用的时候要分清什么是 trusted server。

JaCoCo java agent

JaCoCo 以一个 jar 的形式发布。下载地址,注意它的发布包有 osgi bundle的版本。当做 javaagent 使用,我们应该使用jacocoagent.jar,如果使用它的命令行接口,我们应该使用 jacococli.jar 。它遵循通常的 Java agent 的启动命令语法:

1
-javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]

但如果我们使用 JaCoCo Ant tasks 或者 JaCoCo Maven plug-in

javaagent 可用的 options 见这个表格

JaCoCo Maven plugin

要使用 jacoco 的插件,第一步是获取 maven 的的引用:

1
2
3
4
5
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.3-SNAPSHOT</version>
</plugin>

这个插件自带的 goal 分别是:

  • help
  • prepare-agent(最重要的 goal,大部分情况下只使用这个 goal)
  • prepare-agent-integration
  • merge
  • report-aggregate
  • check
  • dump
  • instrument
  • restore-instrumented-classes

使用这个插件最起码要配的属性:

1
2
3
4
5
6
7
8
9
10
11
<!-- pom 全局的 properties -->
<properties>
<!-- test相关,解决sonar上独立集成测试工程得不到测试覆盖率的问题 -->
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.jacoco.itReportPath>target/jacoco-it.exec</sonar.jacoco.itReportPath>
<sonar.jacoco.reportPath>target/jacoco-ut.exec</sonar.jacoco.reportPath>
<!-- jacoco 是一定要全局配置一个输出 report 的路径的,这里的 jacoco-ut.exec 不是一个可执行文件,而是一个 report 的destFile -->
<jacoco.path>${project.build.directory}/jacoco-ut.exec</jacoco.path>
<jacoco.skip>true</jacoco.skip>
</properties>

然后在使用插件的时候,我们引用了这个 path

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
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.6.0.201210061924</version>
<configuration>
<!-- 这个到底有什么用,存疑。仅仅是越过 jacoco 当前的执行吗? -->
<skip>${jacoco.skip}</skip>
<!-- 复用刚才的数据落点资料 -—>
<destFile>${jacoco.path}</destFile>
<dataFile>${jacoco.path}</dataFile>
<sessionId>jacoco_coverage</sessionId>
</configuration>
<executions>
<execution>
<id>pre-test</id>
<!--在 compile 完成后,处理类的 phase-—>
<phase>process-classes</phase>
<goals>
<!-- 准备 agent -->
<goal>prepare-agent</goal>
</goals>
<configuration>
<!-- 配置好的 agent,要准备放到这个 maven property 里面,这样就可以被其他插件(特别是测试用的 surefire 插件)复用 -->
<!--Allows to specify property which will contains settings for JaCoCo Agent.
If not specified, then &quot;argLine&quot; would be used for &quot;jar&quot; packaging and
&quot;tycho.testArgLine&quot; for &quot;eclipse-test-plugin&quot;.--> <propertyName>coverageAgent</propertyName>
</configuration>
</execution>
</executions>
</plugin>

上面这个 goal 的主要用处就是准备一个 maven property,用来给其他插件作为 VM argument。详细的含义见这里,在这个例子里,argLine 是一个专门为 surefire 准备的 maven property。

这样下面的 surefire 插件本身就可以把这个生成的 maven property 内插进自己的命令行参数里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<argLine>-Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=256m
${coverageAgent}</argLine>
<testFailureIgnore>true</testFailureIgnore>
<includes>
<include>**/*Test*.java</include>
</includes>
</configuration>
</plugin>

更详细的配置(包含如何写 report)
https://www.petrikainulainen.net/programming/maven/creating-code-coverage-reports-for-unit-and-integration-tests-with-the-jacoco-maven-plugin/

参考资料

  1. What is the difference between the Maven Surefire and Maven Failsafe plugins?