多模块项目jacoco 覆盖率统计

写在前面

jacoco是在我们项目中广泛使用的覆盖率统计工具,关于其特点的介绍、持续集成单元测试覆盖率实例及集成测试覆盖率统计的实例在我们的wiki中有详细介绍,在实际项目中,多模块项目的sonar覆盖率常存在统计不全面的情况,本篇将基于一些实践内容来聊一聊多模块项目的覆盖率统计问题,如有不足之处,欢迎大家评论区留言讨论哦。

关于jacoco的更多信息

多模块项目覆盖率统计遇到的问题
知识回顾

首先,简单回顾下maven项目中,maven-jacoco插件的配置

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<properties>
<!--sonar集成-->
<sonar.language>java</sonar.language>
<sonar.dynamicAnalysis>true</sonar.dynamicAnalysis>
<sonar.jacoco.itReportPath>${project.basedir}/target/jacoco.exec</sonar.jacoco.itReportPath>
<!--排除不需要统计覆盖率的部分,:为分割符-->
<sonar.jacoco.excludes>
*.exception.*:*.dto.*
</sonar.jacoco.excludes>
<!--sonar集成end-->
</properties>

<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.4.201502262128</version>
<executions>
<execution>
<!--
在maven的initialize阶段,将Jacoco的runtime agent作为VM的一个参数
传给被测程序,用于监控JVM中的调用。
-->
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<includes>
<includes>com.test.*</includes>
<includes>com.test2.*</includes>
</includes>
<destFile>
${project.basedir}/target/jacoco.exec
</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
<!--
在程序的verify阶段,执行report测试的程序。
文件的输入为perpare-agent阶段中设置或者默认的jacoco.exec.
参数 includes和excludes可用来选定report中过滤的类。
-->
<execution>
<id>default-report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.basedir}/target/jacoco.exec</dataFile>
<outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--
使用 maven-surefire-plugin来执行单元测试。
将surefireArgLine赋值给argLine参数,以保证在测试执行时Jacoco agent处于运行状态。
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>${surefireArgLine}</argLine>
<includes>
<includes>**/*Test*.java</includes>
</includes>
</configuration>
</plugin>

maven项目集成jacoco插件jacoco-maven-plugin,配置插件执行参数,其中${destFile}为jacoco执行过程中产生的数据信息,${dataFile}属性值与${destFile}相同。 在test完成后,jacoco插件会将${dataFile}属性所代表的exec文件转换为html、xml、csv等格式的报告输出在${outputDirectory}属性中。
而由于sonar和jenkins本身都集成了jacoco插件,使用过程中,sonar配置中仅需要指定jacoco exec文件的路径,sonar插件可将exec文件转换为可展示的html文件。

1
<sonar.jacoco.itReportPath>${project.basedir}/target/jacoco.exec</sonar.jacoco.itReportPath>

而jenkins的jacoco配置如图
默认配置会从整个项目中寻找所有的exec文件,并会扫描所有源码和编译后的class文件。当然,如有其它需求,jenkins提供的页面也完全能满足需求了,故项目并不需要对jenkins+jacoco集成再另做配置。

问题引入

简单回顾完项目的覆盖率统计,下面来看看多模块项目会遇到什么问题呢。

来看下我们的项目结构。在我们目前项目中,多模块工程的结构大多是这样的
根目录和每个模块下都有pom文件,根目录的pom为各module的parent pom文件。对于单元测试而言,每个module都会有对应的测试case,在测试阶段,每个module都会生成自己对应的exec文件,按照上文的配置,对每个module的pom文件的jacoco属性进行配置,则exec文件会存在于各自module的target文件夹下。
对于jenkins集成而言,因为可以扫描到所有的exec文件,并会对所有exec文件合并后生成覆盖率报告,多module的工程只要保证有exec文件生成就可正常完成覆盖率统计。
对于sonar及本地maven项目的覆盖率,问题会稍微复杂一点。对于maven项目本地覆盖率统计,因为每个module会有对应的exec文件及报告,查看时只能看到单个module的覆盖率情况,不能得到整个项目的单元测试覆盖率 。对于sonar集成,只能解析最后构建的module的exec文件,不能够统计出整个项目的覆盖率。

如果能将不同module的exec文件合并到一个文件中,并能生成对应的覆盖率报告,sonar集成和本地maven项目覆盖率统计的问题就都能解决了。进而将问题分解为两个,第一,将不同module的exec文件合并到一个文件中,满足sonar统计需求。第二由生成的exec生成覆盖率统计报告或者可以直接生成覆盖率报告,满足本地覆盖率报告查看需求。

多module项目jacoco覆盖率sonar集成

先解决第一个问题,首先从maven属性入手,是否有属性在同一个项目的不同module的取值相同。从maven官方文档看,maven的属性中,与路径相关的有${project.base.dir} ${project.build.directory}这两个属性,这两种分别代表了当前module的源码路径和当前module的target路径,
参考stackoverflow上的高票回答,将所有module的exec文件固定放置在根目录下,具体方案为
父pom中配置sonar的属性,sonar.jacoco.reportPaths设置为${project.basedir}/../target/jacoco.exec,即固定在项目根目录的targe路径下。

1
2
3
4
5
6
7
<properties>
<sonar.language>java</sonar.language>
<sonar.jdbc.username>sonar</sonar.jdbc.username>
<sonar.jdbc.password>sonar</sonar.jdbc.password>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.jacoco.reportPaths>${project.basedir}/../target/jacoco.exec</sonar.jacoco.reportPaths>
</properties>

对于jacoco插件的配置,与上文最大的区别就是加入了append=true的属性,使得不同exec文件可以合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<execution>
<!--
在maven的initialize阶段,将Jacoco的runtime agent作为VM的一个参数
传给被测程序,用于监控JVM中的调用。
-->
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<destFile>
${sonar.jacoco.reportPaths}
</destFile>
<append>true</append>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>

其实到这里,sonar多模块的配置问题得到解决了,统一将不同模块的exec文件放置在根目录的jacoco.exec文件中。sonar依据配置属性拿到的文件是包含所有模块数据信息。

思路扩展

在解决多模块项目jacoco覆盖率统计问题时,查阅到了jacoco command line interface文档,发现jacoco提供了很多有用的命令行,也完全可以通过使用命令行合并不同的exec文件。此处简单介绍一下,仅为思路扩展。

1
java -jar jacococli.jar merge [<execfiles> ...] --destfile <path> [--help] [--quiet]

jacoco 提供了很多命令行接口,与报告合并的命令如上所示,merge 命令。结合jenkins的exectue shell功能,也可以解决多模块sonar jacoco覆盖率统计问题。做法具体为:

  • 下载jacoco.zip包到jenkins所在机器上,解压后得到jacococli.jar 文件,路径为 /AAAA/bbb
  • 在build pom 中执行构建和测试工作,在每个module中生成exec文件
  • 在build pom的post step中添加 execute shell
    1
    2
    3
    4
    5
    6
    7
    8
    java -jar /AAAA/bbb/jacococli.jar merge ${project-dir}/{$module1}/target/jacoco.exec ${project-dir}/{$module2}/target/jacoco.exec ...${project-dir}/{$modulen}/target/jacoco.exec --destfile ${project-dir}/jacoco.exec
    cp -f ${project-dir}/jacoco.exec ${project-dir}/{$module1}/target/jacoco.exec
    cp -f ${project-dir}/jacoco.exec ${project-dir}/{$module2}/target/jacoco.exec
    .
    .
    .
    cp -f ${project-dir}/jacoco.exec ${project-dir}/{$modulen}/target/jacoco.exec
    mvn sonar:sonar

这个解决方法通过命令行将不同模块的exec数据文件合并,并将合并后的文件替换掉各个模块原有文件,之后再执行sonar:sonar命令。方法并不优雅,只是提供一种额外思路。遇到问题总有办法能解决,多看文档没有坏处^ ~ ^。

多模块项目本地覆盖率报告问题解决

关于jacoco多模块覆盖率报告的问题,jacoco在17年增加了对多模块项目覆盖率的支持,具体见官方文档。依据官方文档方法,具体操作为:

  • 选择其中一个模块,或者新增一个虚拟模块,确保该模块依赖于该项目的所有其他模块,这样,该模块在生成报告时,其他模块已经执行完成测试和exec数据生成。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <dependencies>
    <dependency>
    <groupId>xxx</groupId>
    <artifactId>module1</artifactId>
    <version>${project.version}</version>

    </dependency>
    <dependency>
    <groupId>xxx</groupId>
    <artifactId>module2</artifactId>

    </dependency>
    <dependency>
    <groupId>xxx</groupId>
    <artifactId>modulen</artifactId>
    </dependency>

    .
    .
    .

    </dependencies>
  • 在选定的module中添加jacoco-maven插件的相关配置,设置目标report-aggregate,关联maven的生命周期 verify

    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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.7.8</version>
    <executions>
    <execution>
    <!--
    在maven的initialize阶段,将Jacoco的runtime agent作为VM的一个参数
    传给被测程序,用于监控JVM中的调用。
    -->
    <id>default-prepare-agent</id>
    <goals>
    <goal>prepare-agent</goal>
    </goals>
    <configuration>
    <destFile>
    ${sonar.jacoco.reportPaths}
    </destFile>
    <append>true</append>
    <propertyName>surefireArgLine</propertyName>
    </configuration>
    </execution>
    <!--
    在程序的verify阶段,执行report测试的程序。
    文件的输入为perpare-agent阶段中设置或者默认的jacoco.exec.
    参数 includes和excludes可用来选定report中过滤的类。
    -->
    <execution>
    <id>default-report</id>
    <phase>prepare-package</phase>
    <goals>
    <goal>report</goal>
    </goals>
    <configuration>
    <dataFile>${sonar.jacoco.reportPaths}</dataFile>
    <outputDirectory>${project.reporting.outputDirectory}/target/site/jacoco</outputDirectory>
    </configuration>
    </execution>
    <execution>
    <phase>verify</phase>
    <goals>
    <goal>report-aggregate</goal>
    </goals>
    </execution>
    </executions>
    </plugin>

需要注意的是,report-aggregate 这个maven 目标是在jacoco 0.7.7以后才有的功能。

  • 执行项目构建
    1
    mvn clean verify -P$profile

构建完成后,在选定模块的target/site/jacoco文件夹中得到jacoco.html文件。打开,即能在本地查阅多模块项目的覆盖率。

其他可能遇到的问题

在多模块项目中,有的模块可能不需要单元测试,并没有测试用例,但如果在父pom中统一配置了maven-surefire-plugin插件,运行mvn test时,执行到该模块测试时,可能会因为没有找到测试用例而失败。
此处,提供一种可以跳过测试的方法:在模块的pom文件中添加profile,设置跳过测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<profile>
<id>noTest</id>
<activation>
<property>
<name>noTest</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.9</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>

项目执行测试时,执行如下命令即可

1
mvn test -P$profile -DnoTest=true

至此,多模块项目jacoco单测覆盖率统计问题解决了。

扩展—单元测试与服务端集成测试覆盖率合并及sonar、jenkins集成

对于手动场景及集成测试场景自动化测试的覆盖率统计,我们用内部开发的jenkins插件可以dump出报告,不过按照现有的流程,这部分覆盖率还无法在sonar平台上展示,更不能与开发的覆盖率合并显示。前面提到过jacoco command line interface有很多jacoco命令,结合这些命令可以实现sonar展示测试代码覆盖率,及开发测试覆盖率合并。下面将详解介绍配置方法。

以普通服务A为例,A服务的代码仓库中包含源码和所有的测试代码。

  • step 1: 进入jacoco首页,下载新版本的jacoco-xxx.zip包,解压后,取jacococli.jar包,放置在jenkins 所在机器上,目录为/home/jenkins/jacococli.jar, 路径可随意,jenkins 有权限访问即可。取jacocoagent.jar,放在在A服务启动机器${your-server-ip}上,路径
  • step 2: 修改服务A的启动脚本脚本,在jvm启动参数中,添加jacoco agent相关参数 “-javaagent:/home/jenkins/jacocoagent.jar=includes=pack.*,output=tcpserver,port=${jacoco-agent-port},address=${your-server-ip}”
  • step 3:jenkins job配置注意事项,在build步骤前新增 per-step,选择execute shell,添加jenkins 自动部署脚本

    1
    $DEPLOY

    在pom build 项目构建中选择执行测试,即运行所有单元测试及集成测试case

    1
    clean package -P${profile}

    在build后添加post step,选择 execute shell,添加脚本

    1
    2
    3
    4
    java /home/jenkins/jacococli.jar dump --address ${your-server-ip} --destfile ./target/jacoco-dump.exec --port ${jacoco-agent-port}
    java -jar /home/jenkins/jacococli.jar merge ./target/jacoco-dump.exec ./target/jacoco.exec --destfile ./targe/jacoco-aggre.exec
    mv ./target/jacoco-aggre.exec ./target/jacoco.exec
    mvn sonar:sonar

    将上述变量替换为实际使用的变量,其中your-server-ip 指的是远端服务部署的机器ip,jacoco-agent-port指的是远端服务的jacoco agent监听端口。
    脚本先将远端服务的测试执行数据文件dump到本地 ./target/jacoco-dump.exec,merge 本地单元测试数据文件 ./target/jacoco.exec 和dump下的执行数据文件./target/jacoco-dump.exec 为./targe/jacoco-aggre.exec 文件,为了兼容pom中sonar的配置,将合并后的文件重命名为jacoco.exec,然后添加 mvn sonar:sonar启动sonar分析和结果收集。

    job的其他配置不需要改变,build 完成后即可在sonar和jenkins上看到完整的覆盖率报告。

总结

本文介绍了多模块项目代码覆盖率sonar集成和本地构建时遇到的问题,分别给出了解决方法。在文末,扩展引申讨论了单元测试覆盖率和测试覆盖率合并统计的问题,结合jacoco官方文档,给出一些建议做法。以上内容,验证可用。如有问题,欢迎留言区讨论~~