jacoco统计代码覆盖率的文章一搜一大堆,方法也很简单,就是在pom中引用两个插件:

maven-surefire-plugin

jacoco-maven-plugin

其中jacoco-maven-plugin的关键配置为要有两个execution:

一个goal是prepare-agent,即准备agent,实现为代码插桩

一个goal是report,顾名思义,即生成覆盖率报告

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
            <skipTests>false</skipTests>
            <testFailureIgnore>true</testFailureIgnore>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.5</version>
        <executions>
            <execution>
                <id>my-prepare-agent</id>
                <goals>
                    <goal>prepare-agent</goal>
                </goals>
            </execution>
            <execution>
                <id>my-report</id>
                <phase>test</phase>
                <goals>
                    <goal>report</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

但是如果是多模块的工程呢?如下面的结构:

├── module1
│   └── pom.xml
├── module2
│   └── pom.xml
├── module3
│   └── pom.xml
├── pom.xml

每个模块都这么配置的话,生成的报告是各自独立的,即会生成3个报告,那么怎么把各个模块的代码覆盖率统计在一起,生成一个聚合的报告呢?


jacoco提供了报告聚合的能力(goal为report-aggregate,注意与上面提到的report的区别),这就需要有一个模块来做报告聚合,这个模块需要引用所有的其他模块(以上面举例的工程为例,这个模块可以是新增的module4,也可以在module1/2/3中任选一个,一般在多模块的工程中,都会有一个主模块做为启动模块,可以用这个启动模块来做jacoco的报聚合)

划一下重点:

  1. 一个模块配置jacoco的report-aggregate
  2. 这个模块需要引用所有的其他模块

具体做法如下:

第一:在项目根目录下的父pom中添加jacoco-maven-plugin,并配置goal是prepare-agent(这么做的好处是不用在每个module的pom中都重复添加同样的内容)

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
            <skipTests>false</skipTests>
            <testFailureIgnore>true</testFailureIgnore>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.5</version>
        <executions>
            <execution>
                <id>my-prepare-agent</id>
                <goals>
                    <goal>prepare-agent</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

第二:在聚合模块中配置jacoco聚合报告:

<plugins>
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.5</version>
        <executions>
            <execution>
                <id>my-report</id>
                <phase>test</phase>
                <goals>
                    <goal>report-aggregate</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

第三:在聚合模块中引用其他模块(非常重要,只有引用的模块的覆盖率才会被聚合到报告中,未引用的模块的覆盖率不会被收集和聚合):

<dependencies>
    <dependency>
      <groupId>@project.groupId@</groupId>
      <artifactId>module1</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>@project.groupId@</groupId>
      <artifactId>module2</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>@project.groupId@</groupId>
      <artifactId>module3</artifactId>
      <version>${project.version}</version>
    </dependency>
 </dependencies>

但是我按照上述文章提到的方法配置后,可以得到聚合的报告了,但是覆盖率都为0,这又是怎么回事呢?

springboot多模块如何查看依赖关系 springboot多模块包扫描_java

搜索了很多文章都无解,最后查看jacoco的help才找到答案:

这里需要涉及另外一个插件:maven-surefire-plugin

需要在maven-surefire-plugin中配置引用jacoco插件的输出属性argLine

原理分析:jacoco在prepare-agent阶段会生成一个属性,这个属性指向jacoco的runtime agent,默认这个属性叫argLine,我们需要在maven-surefire-plugin的配置中,引用这个属性,这样在surefire执行测试时,才会能够找到并挂载jacoco的agent,从而实现代码覆盖率的统计(当然我们也可以自定义这个属性的名字)

<configuration>
          <argLine>${argLine}</argLine>
        </configuration>

最终在根目录的pom.xml是这样的

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
            <skipTests>false</skipTests>
            <testFailureIgnore>true</testFailureIgnore>
            <argLine>${argLine}</argLine>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.5</version>
        <executions>
            <execution>
                <id>my-prepare-agent</id>
                <goals>
                    <goal>prepare-agent</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>

或者在prepare-agent阶段自定义jacoco输出的属性名,那么你的根pom文件是这样的:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
            <skipTests>false</skipTests>
            <testFailureIgnore>true</testFailureIgnore>
            <argLine>${jacocoArgLine}</argLine>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.5</version>
        <executions>
            <execution>
                <id>my-prepare-agent</id>
                <goals>
                    <goal>prepare-agent</goal>
                </goals>
                <configuration>
                  <propertyName>jacocoArgLine</propertyName>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

好了,这时再去运行一下:

mvn test -Dmaven.test.failure.ignore=true

看看聚合报告的结果吧。

jacoco帮助命令行提示原文如下,有兴趣的英文好的同学可以看看

jacoco:prepare-agent
   Prepares a property pointing to the JaCoCo runtime agent that can be passed as
   a VM argument to the application under test. Depending on the project
   packaging type by default a property with the following name is set:
   
   - tycho.testArgLine for packaging type eclipse-test-plugin and
   - argLine otherwise.
   
   If your project already defines VM arguments for test execution, be sure that
   they will include property defined by JaCoCo.
   
   One of the ways to do this in case of maven-surefire-plugin - is to use syntax
   for late property evaluation:
   
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
    <argLine>@{argLine} -your -extra -arguments</argLine>
    </configuration>
    </plugin>
   
   You can define empty property to avoid JVM startup error Could not find or
   load main class @{argLine} when using late property evaluation and
   jacoco-maven-plugin not executed.
   
   Another way is to define 'argLine' as a Maven property rather than as part of
   the configuration of maven-surefire-plugin:
   
    <properties>
    <argLine>-your -extra -arguments</argLine>
    </properties>
    ...
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
    <!-- no argLine here -->
    </configuration>
    </plugin>
   
   Resulting coverage information is collected during execution and by default
   written to a file when the process terminates.