yokila
yokila
Published on 2023-11-09 / 10 Visits
0
0

JaCoCo的Maven应用入门使用教程

一、前言 

1、本文简介 

本文先行介绍什么是JaCoCo与代码覆盖率,然后基于Maven项目,介绍JaCoCo相关依赖包和插件,并示例如何在Spingboot项目中应用JaCoCo工具。

本文示例基于的环境:IDEA 社区版 2021.3.1、Java1.8、SpringBoot 2.6.0、Junit 5.8.1、Maven 3.8.4

2、官网地址

https://www.eclemma.org/jacoco/trunk/index.html

3、什么是JaCoCo

缩写自Java Code Coverage,是一个免费的 Java 代码覆盖库。

4、关于代码覆盖率

代码覆盖率,是软件测试中的一个重要度量,指的是通过计算测试过程中被执行的源代码占全部源代码的比例,进而间接度量软件质量的方法。

该方法广泛用于评估单元测试的覆盖率,此时测试用例由开发人员编写实施,属于白盒测试

可以顺便去看邹欣写的这本《构建之法 现代软件工程》,其中第二章有提及相关的一些内容:

关于代码覆盖率的更多知识,可以自行搜索阅读,本文篇幅有限,不展开描述。

推荐补充阅读:什么是代码覆盖率

5、注意

虽然本文是介绍用JaCoCo来提高测试覆盖率,以此提高代码质量。但是,无需强求百分百的代码覆盖率。只要实践过,就会发现,项目仅需大一点,测试用例的量就会以几何倍数增长,很难或者说需要花费很大代价才能达到很高的覆盖率。所以,实践过程中,需要进行权衡。

二、JaCoCo的依赖包和插件

Maven仓库中的JaCoCo页面(在这里主要是可以直接获得各个版本的POM引入写法示例以及各个依赖包等的介绍,还有最重要的就是可以看到各个依赖包之间的相互依赖关系):https://central.sonatype.com/namespace/org.jacoco

1、jacoco

官方简介:JaCoCo Standalone Distribution

该依赖包是完整的JaCoCo依赖包,内部包含了所有JaCoCo的依赖包,如果不清楚到底要依赖哪些JaCoCo的包,那么可以直接引入这个包的依赖(当然,这样的的话,你的jar包就会偏大,毕竟很可能引入了实际用不上的内容)。

另外,底下的这些包,实际上就是JaCoCo大大小小的模块。

2、jacoco-maven-plugin

若是Maven构建项目,一般只需要引入这个插件就行了。

插件简介:The JaCoCo Maven Plugin provides the JaCoCo runtime agent to your tests and allows basic report creation

包含:build、core、agent、report

官网介绍地址https://www.jacoco.org/jacoco/trunk/doc/maven.html

注意点击阅读官网介绍页面最底下的这些可配置的goals的页面:

运行JUnit测试并生成单元测试覆盖率报告的POM文件内容官方示例地址https://www.jacoco.org/jacoco/trunk/doc/examples/build/pom.xml

运行单元测试和集成测试,并创建两个覆盖率报告的POM文件内容官方示例地址https://www.jacoco.org/jacoco/trunk/doc/examples/build/pom-it.xml

环境要求:Maven 3.0 或更高版本,以及Java 1.8 或更高版本用于 Maven 运行时,Java 1.5 或更高版本用于测试执行程序。

3、org.jacoco.cli

依赖包简介:JaCoCo Command Line Interface

包含:build、core、report

4、org.jacoco.ant

依赖包简介:JaCoCo Ant Tasks

包含:build、core、report、agent

官网介绍地址https://www.jacoco.org/jacoco/trunk/doc/ant.html

Ant:类似make/nmake的一种构建工具,主要就是用来构建JAVA程序。本文主要提及的Maven就是在Ant之后登场的一个构建工具,再后面现在还有一个就是Gradle。感兴趣可以顺带去搜索了解一下。

回到这个依赖包,如果你使用的是Ant进行Java项目的构建,那么就会用它。它相当于帮你定义了一个任务,可以用于启动带有执行记录的 Java 程序 以及根据记录的数据创建覆盖率报告。执行数据可以通过任务coverage、agent、dump 和 merge 进行收集和管理。不同格式的报告是使用report任务创建。对于离线检测,任务instrument可用于准备类文件。

由于本文不谈及Ant,所以此处不扩展。

5、org.jacoco.agent

依赖包简介:JaCoCo Agent

包含:build

官网介绍地址https://www.jacoco.org/jacoco/trunk/doc/agent.html

JaCoCo是针对编译后生成的Class文件进行执行并记录覆盖率数据。agent就是负责进行这个即时检测的操作。(检测的最小单位是 Java 字节码指令)

JaCoCo agent 将收集执行信息,并且根据请求或者当JVM 退出时转储收集的数据信息。执行数据输出有三种不同的模式:

①文件系统:在 JVM 终止时,执行数据将写入本地 文件。

②TCP 套接字服务器:外部工具可以连接到 JVM 并通过Socket连接获得执行数据。执行数据重置和执行数据转储在VM 退出时是可能发生的。

③TCP 套接字客户端:在启动时,JaCoCo agent连接到给定的 TCP 端点,执行数据会通过通过Socket连接发送过去。VM 退出时,执行数据重置和执行数据转储是可能发生的。

6、org.jacoco.report

依赖包简介:JaCoCo Report

包含:build、core

负责生成报告文件。

7、org.jacoco.core

依赖包简介:JaCoCo core APIs and implementations

包含:build

8、org.jacoco.build

依赖包介绍:JaCoCo - Java Code Coverage Library

注:作为一个JaCoCo其他依赖包公共引入的Parent,里面包含了很多公共的信息。建议可以去看看这个包的pom.xml文件内容。

三、Maven导入JaCoCo

参考官网文档:https://www.jacoco.org/jacoco/trunk/doc/examples/build/pom.xml

其实有没有发现,前文有提及这个文档。对的,官方是有特意提供了Maven构建里使用JaCoCo的示例。

作为初学者,只需要直接引入jacoco-maven-plugin这个插件就好了

<plugin>
   <groupId>org.jacoco</groupId>
   <artifactId>jacoco-maven-plugin</artifactId>
   <version>0.8.11</version>
</plugin>

我们还可以对其做一些配置(这里只列举几个,具体的可配置项在前文jacoco-maven-plugin中已有展示)。

注意:有些配置是有默认值的,实在不懂的话,还是不要乱改动为好。

1、prepare-agent

地址:https://www.jacoco.org/jacoco/trunk/doc/prepare-agent-mojo.html

作用:准备指向 JaCoCo 运行时代理的属性,该属性也可以作为 VM 参数传递给受测应用程序。其实就是配置agent的属性,为等下运行测试用例的检测做准备。

:需要版本>=0.5.3

:默认情况下绑定到Maven生命周期阶段的initializeMaven生命周期可参考官方的文档《Lifecycles Reference》

使用示例

<execution>
    <id>prepare-agent</id>
    <goals>
        <goal>prepare-agent</goal>
    </goals>
    <configuration>
        <!--
            字段的值应该是使用标准通配符语法相对于目录target/classes/的已编译类的类路径(而不是程序包名称)
            不配置就是全部类
            *   Match zero or more characters
            **  Match zero or more directories
            ?   Match a single character
        -->
   	     <includes>
             com/basic/happytest/modules/jaCoCo/*
        </includes>
        <destFile><!-- 这里其实是默认值 -->
            ${project.build.directory}/jacoco.exec
        </destFile>
        <skip>
            false
         </skip>
    </configuration>
 </execution>

2、report

地址https://www.jacoco.org/jacoco/trunk/doc/report-mojo.html

作用:为单个项目的测试创建代码覆盖率报告,支持多种格式(HTML、XML 和 CSV)。如果是集成测试之类的情况,则用的是其他配置。(其实就是去加载agent生成的jacoco.exec文件,然后创建出来可阅读的格式的报告文件)

:需要版本>=0.5.3

:默认情况下绑定到Maven生命周期阶段的verify

使用示例

<execution>
    <id>report</id>
    <phase>verify</phase>
    <goals>
        <goal>report</goal>
    </goals>
    <configuration>
        <outputDirectory> <!-- 默认值 -->
            ${project.reporting.outputDirectory}/jacoco
        </outputDirectory>
        <dataFile> <!-- 默认值 -->
            ${project.build.directory}/jacoco.exec
        </dataFile>
        <!--
           指定只准备生成符合匹配规则的内容的报告
           不指定的话默认报告包含全部类(无视surefire、prepare-agent等其他的限制范围)
        -->
        <includes>
           com/basic/happytest/modules/jaCoCo/*
        </includes>
        <footer>
            Footer text used in HTML report pages.
        </footer>
        <formats>
            HTML
        </formats>
        <outputEncoding> <!-- 默认值 -->
            UTF-8
        </outputEncoding>
        <sourceEncoding> <!-- 默认值 -->
            UTF-8
        </sourceEncoding>
        <title> <!-- 默认值 -->
            ${project.name}
        </title>
    </configuration>
</execution>

3、check

地址https://www.jacoco.org/jacoco/trunk/doc/check-mojo.html

作用:检查最终的代码覆盖率是否到达了设定的指标。如果项目没有指标需求,可以不配置,也就等同于不做这个检查。

:需要版本>=0.6.1

:默认情况下绑定到Maven生命周期阶段的verify

使用示例

<execution>
    <id>jacoco-check</id>
    <phase>verify</phase>
    <goals>
        <goal>check</goal>
    </goals>
    <configuration>
        <includes> <!-- 只检查符合匹配规则的内容的项,不选的话会扫描全部,无视其他配置的限制范围 -->
            com/basic/happytest/modules/jaCoCo/*
        </includes>
        <haltOnFailure> <!-- 默认值 -->
            true
        </haltOnFailure>
        <rules>
            <rule>
                <element>CLASS</element>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>0.80</minimum>
                    </limit>
                </limits>
            </rule>
        </rules>
    </configuration>
</execution>

:作者很好奇官网提及的elment中可选的类型里BUNDLE指的是什么,是OSGi 体系架构里的Bundle吗?(留待高人解答)

如果通过设置的指标,会在日志里显示:

4、instrument

地址https://www.jacoco.org/jacoco/trunk/doc/instrument-mojo.html

专门的官方文档https://www.jacoco.org/jacoco/trunk/doc/offline.html

作用:进行离线检测代码覆盖率,存在一些缺陷,只有特定情况下才用。官方建议常规情况下是使用runtime的方式,而不是这种方式。所以这里无示例。

四、生成报告

如果你的测试方法原先写得可能不那么规范,不是可以循环使用的,那么就需要利用maven-surefire-plugin这个插件来排除一些可能会出问题的测试类,或者包含进一些测试类。(当然,这是无可奈何的情况下的举措,实际上,就应该规范书写测试类,然后进行全项目的覆盖率检测)(不过这样操作,实际上JaCoCo的配置也要跟着限制范围,导致最终生成的报告只是项目里的某个部分的报告)

当然,上面提及的属于特殊情况。

正常情况下不用考虑。

操作的话,直接在IDEA里点击右侧的Maven标签,然后在打开的窗口里,点击clean,再点击verify(具体取决于你生成report配置绑定的maven的生命周期),就可以生成覆盖率报告了。

或者使用maven命令的方式,直接执行 mvn verify命令(具体取决于你生成report配置绑定的maven的生命周期),可以一次性执行所有的测试用例,同时也将生成覆盖率报告。

生成覆盖率报告的页面操作

生成的覆盖率报告位置,取决于前文提及的你在report的配置里,设置的报告输出目标位置

配置示例

打开文件目录,可以看到对应的文件夹

示例

进入文件夹后,可以看到一个index.html的文件,双击点开

文件夹内容示例

浏览器中,将显示覆盖率报告内容信息

浏览器显示示例

可以点击包名、类名、方法,一步步进入到对应页面,可以看更详细的覆盖情况的信息

示例

五、未生成报告问题解决

1、情况:跳过了测试

不能跳过测试,跳过测试的话,自然就没覆盖率报告了。

配置实例

跳过测试的情况下,执行日志里可以看到:

日志示例

2、情况:执行没到生成报告的生命周期

JaCoCo每个步骤都默认绑定到了Maven的生命周期中的一个阶段(或者你在配置里面改了绑定的阶段位置),如果你执行的Maven命令没到达这个阶段,就不会执行这个步骤。

例如按照下图的配置,若执行的是mvn test,由于生命周期没到达verify这个阶段,就不会执行report、jacoco-check这两个步骤,除非换成执行mvn verify或者把repot、jacoco-check改成绑定到test这个阶段上,这样就能正常生成报告了。

配置示例

3、情况:生成报告了,但是又没完全生成报告

显示内容:No class files specified.

这种情况其实可以通过执行日志来查看具体是什么情况。

一般是JaCoCo的各个步骤都正常进行,但是你配置了includes和excludes后,导致最终没有符合匹配的项,于是就没有Class可以进行分析,所以就显示这个结果。

打开报告后的页面示例

这种情况下,执行日志可以看到:

六、覆盖率报告解读

官网对覆盖率数据介绍的地址:https://www.jacoco.org/jacoco/trunk/doc/counters.html

图源自官网

官网其实已经讲得很详细了,也没什么需要补充的内容。

七、扩展

1、 IDEA中jacoco.exec的使用

IDEA中,点击上方菜单项的Run,然后在打开的面板中选择Show Coverage Data

在弹出的页面中,选择“+

此时,需要找到生成的jacoco.exec文件的路径,选中它,点击“OK”。

勾选jacoco.exec,并点击Show selected

此时,IDEA的项目页面就会出现覆盖率的数据信息,如下图所示。

若要关闭显示覆盖率信息,则可以再次点击Run,然后点击Hide coverage,就可以了。

2、完整的一个JaCoCo的POM示例配置(本文覆盖率报告基于的配置)

注意:本配置仅供参考,建议阅读本文,同时基于代码上的相关注释来进行自定义修改。

<!-- 一个用于mvn生命周期的测试阶段的插件,可以通过一些参数设置方便的在testNG或junit下对测试阶段进行自定义 -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M5</version>
    <configuration>
        <!--
            默认情况下,Surefire 插件将自动包含具有以下通配符模式的所有测试类:
            "**/Test*.java"- 包括其所有子目录和所有以“Test”开头的 Java 文件名。
            "**/*Test.java"- 包括其所有子目录和所有以“Test”结尾的 Java 文件名。
            "**/*Tests.java"- 包括其所有子目录和所有以“Tests”结尾的 Java 文件名。
            "**/*TestCase.java"- 包括其所有子目录和所有以“TestCase”结尾的 Java 文件名。
            如果测试类不遵循默认通配符模式,则才需要额外包含以下文件
        -->
        <includes>
            JaCoCoInstanceT.java
        </includes>
        <excludes>
            <exclude>**/Test*.java</exclude>
            <exclude>**/*Test.java</exclude>
            <exclude>**/*Tests.java</exclude>
            <exclude>**/*TestCase.java</exclude>
        </excludes>
        <!-- 注意,如果想要输出代码覆盖率报告,则此处应该为false -->
        <!-- 跳过测试阶段 -->
        <skipTests>false</skipTests>
        <!-- 参数 forkCount 定义 Surefire 为执行测试而同时生成的 JVM 进程的最大数量 -->
        <!-- JaCoCo要求不能为0,此处设置的其实是默认值 -->
        <forkCount>1</forkCount>
        <!-- JaCoCo要求不能是Never,此处设置的其实是默认值 -->
        <forkMode>once</forkMode>
    </configuration>
</plugin>

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution>
            <id>prepare-agent</id> <!-- 唯一标识这个特定的执行。这主要用于在需要的地方引用或区分多个执行。 -->
            <phase>initialize</phase> <!-- 定义应该在哪个生命周期阶段执行该插件的目标 -->
            <goals> <!-- 列出应该执行的目标 -->
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <!--
                    字段的值应该是使用标准通配符语法相对于目录target/classes/的已编译类的类路径(而不是程序包名称)
                    *   Match zero or more characters
                    **  Match zero or more directories
                    ?   Match a single character
                -->
                <includes> <!-- 只准备扫描符合匹配规则的内容 -->
                    com/basic/happytest/modules/jaCoCo/*
                </includes>
                <destFile><!-- 这里其实是默认值 -->
                    ${project.build.directory}/jacoco.exec
                </destFile>
                <skip> <!-- 此处该配置使用的是默认值 -->
                    false
                </skip>
            </configuration>
        </execution>
        <execution>
            <id>merge</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>merge</goal>
            </goals>
            <configuration>
                <fileSets> <!-- 此处该配置使用的是默认值 -->
                    <fileSet>
                        <directory>${project.build.directory}</directory>
                        <includes>
                            <include>*.exec</include>
                        </includes>
                    </fileSet>
                </fileSets>
                <skip> <!-- 此处该配置使用的是默认值 -->
                    false
                </skip>
            </configuration>
        </execution>
        <execution>
            <id>report</id>
            <phase>verify</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <outputDirectory>
                    ${project.build.directory}/jacoco
                </outputDirectory>
                <dataFile> <!-- 默认值 -->
                    ${project.build.directory}/jacoco.exec
                </dataFile>
                <!--
                    指定只准备生成符合匹配规则的内容的报告
                    不指定的话默认报告包含全部类(无视surefire、prepare-agent等其他的限制范围)
                -->
                <includes>
                    com/basic/happytest/modules/jaCoCo/*
                </includes>
                <footer> <!-- 会显示在报告页面的最下面 -->
                    Footer text used in HTML report pages.
                </footer>
                <formats>
                    HTML
                </formats>
                <outputEncoding> <!-- 默认值 -->
                    UTF-8
                </outputEncoding>
                <sourceEncoding> <!-- 默认值 -->
                    UTF-8
                </sourceEncoding>
                <title> <!-- 默认值 -->
                    ${project.name}
                </title>
            </configuration>
        </execution>
        <execution>
            <id>jacoco-check</id>
            <phase>verify</phase>
            <goals>
                <goal>check</goal>
            </goals>
            <configuration>
                <includes> <!-- 只检查符合匹配规则的内容的项,不选的话会扫描全部,无视其他配置的限制范围 -->
                    com/basic/happytest/modules/jaCoCo/*
                </includes>
                <haltOnFailure> <!-- 默认值 -->
                    true
                </haltOnFailure>
                <rules>
                    <rule>
                        <element>CLASS</element>
                        <limits>
                            <limit>
                                <counter>LINE</counter>
                                <value>COVEREDRATIO</value>
                                <minimum>0.80</minimum>
                            </limit>
                        </limits>
                    </rule>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>



Comment