jacoco统计覆盖率最佳实践

jacoco统计覆盖率最佳实践

  • jacoco总结
    • Jacoco安装
    • 代码覆盖率介绍
      • 行覆盖
      • 分支覆盖
      • 方法覆盖
      • 覆盖率的误区
      • 代码覆盖率意义
    • 覆盖率报告解析
    • 启动jacoco agent进行插桩
      • **单元测试offline模式:**
        • mvn命令增加参数
      • 在pom文件中添加jacoco插件
      • **功能测试on-the-fly模式**:
        • 启jacoco命令:
        • dump - 生成exec文件
        • report - 生成报告
        • instrument - Java类文件和JAR文件的离线检测。
        • merge - 将多个exec文件合并到一个新的exec文件中。
        • classinfo - 在提供的位置打印有关Java类文件的信息。
        • execinfo - 以可读格式打印exec文件内容

jacoco总结

JaCoCo是面向Java的开源代码覆盖率工具,JaCoCo以Java代理模式运行,它负责在运行测试时检测字节码。 JaCoCo会深入研究每个指令,并显示每个测试过程中要执行的行。 为了收集覆盖率数据,JaCoCo使用ASM即时进行代码检测,并在此过程中从JVM Tool Interface接收事件,最终生成代码覆盖率报告。

jacoco运行有离线(offline)、在线(on the fly)模式之说,所谓在线模式就是在应用启动时加入jacoco agent进行插桩,在开发、测试人员使用应用期间实时地进行代码覆盖率分析。相信很多的java项目开发人员并不会去写单元测试代码的,因此覆盖率统计就要把手工测试或接口测试覆盖的情况作为重要依据,显然在线模式更符合实际需求,本文以在线模式为例进行演示。

Jacoco安装

从官网
https://www.jacoco.org/jacoco/下载最新版本并解压到指定目录

代码覆盖率介绍

覆盖率是用来衡量测试代码对功能代码的测试情况。通过统计测试代码中对功能代码中行、分支、类等模拟场景数量,来量化说明测试的充分度。

代码覆盖率=代码的覆盖程度,一种度量方式。

覆盖率简单说:跑了一个测试用例,项目代码中的那些模块、文件、类、方法 执行了。

其中行覆盖率是最细颗粒度,其他覆盖率都可从行覆盖率情况统计算出来。

行覆盖

当至少一个指令被指定源码行执行时,该源码行被认为已执行。

通俗的说就是,测试行为,触发了这某一行代码,则这一行代码就被覆盖了。

分支覆盖

if 和switch 语句算作分支覆盖率,这个指标计算一个方法中的分支总数,并决定已执行和未执行的分支的数量。

全部未覆盖:所有分支均未执行,红色标志

部分覆盖:部分分支被执行,黄色标志

全覆盖:所有分支均已执行,绿色标志。

方法覆盖

当方法中至少有一个指令被执行,该方法被认为已执行,包括构造函数和静态初始化方法。

覆盖率的误区

若代码如下:

if (i>100)
    j = 10/i        // 没有除0错误
else
    j = 10/(i+2)    // i==-2,除0错误

覆盖2个分支,只需要设计i101 和 i1,但是对于找到i==-2这个bug点时没有作用的。

所以:

1、不要简单的追求高的代码覆盖率

2、高覆盖率测试用例不等于测试用例有效

3、没覆盖的分支相当于该分支上的任何错误肯定都找不到

代码覆盖率意义

分析未覆盖部分的代码,反推测试设计是否充分,没有覆盖到的代码是否存在测试设计盲点。

覆盖率报告解析

jacoco统计覆盖率最佳实践_第1张图片

  • Instructions: Java 字节指令的覆盖率。执行的最小单位,和代码的格式无关。

  • Branches: 分支覆盖率。注意,异常处理不算做分支。

  • Cxty(Cyclomatic Complexity): 圈复杂度, Jacoco 会为每一个非抽象方法计算圈复杂度,并为类,包以及组(groups)计算复杂度。

    圈复杂度简单的说就是为了覆盖所有路径,所需要执行单元测试数量,圈复杂度大说明程序代码可能质量低且难于测试和维护。

  • Lines: 行覆盖率,只要本行有一条指令被执行,则本行则被标记为被执行。

  • Methods: 方法覆盖率,任何非抽象的方法,只要有一条指令被执行,则该方法被计为被执行。

  • Classes: 类覆盖率,所有类,包括接口,只要其中有一个方法被执行,则标记为被执行。注意:构造函数和静态初始化块也算作方法。

类角度相信信息

jacoco统计覆盖率最佳实践_第2张图片

  • 钻石代表分支覆盖情况、背景颜色代表指令覆盖率

  • 红色钻石/背景:这一行没有分支/指令被执行

  • 黄色钻石/背景:这一行中只有部分分支/指令被执行

  • 绿色钻石/背景:这一行的所有分支/指令都被执行

启动jacoco agent进行插桩

jacoco使用插桩的方式来记录覆盖率数据,是通过一个probe探针来注入。jacoco有2种插桩模式

,offline模式和on-the-fly模式。

单元测试offline模式:

对应的是jacoco的offline模式。

offline模式就是在测试之前先对文件进行插桩,生成插过桩的class或jar包,测试插过桩的class和jar包,生成覆盖率信息到文件,最后统一处理,生成报告。

在脱机模式下,可以使用与代理可用的相同财产集配置JaCoCo运行时,除了类文件已经插入指令的include/excludes选项之外。有两种不同的方式来提供配置:

配置文件:如果在类路径选项中提供了文件jacoco-agent.properties,则从该文件加载。文件必须格式化为Java配置文件格式。

系统配置:选项也可以作为Java系统配置提供。在这种情况下,选项必须以“jacoco-agent”为前缀。例如,*.exec文件的位置可以用系统属性“jacoco-angent.destfile”配置。

<project>
  <reporting>
    <plugins>
      <plugin>
        <groupId>org.jacocogroupId>
        <artifactId>jacoco-maven-pluginartifactId>
        <reportSets>
          <reportSet>
            <reports>
              
              <report>reportreport>
            reports>
          reportSet>
        reportSets>
      plugin>
    plugins>
  reporting>
project>

离线调用jacoco有两种方式,第一种是命令行方式调用jacoco的插件,另外一种是直接在maven中配置jacoco插件。

之前项目中曾经调研过其实还有第三种方式,我是在maven调用是开启了jacoco的代理在maven参数中,官方没有写这样用的方式所以不建议。

mvn命令增加参数

在执行mvn命令时,加上“org.jacoco:jacoco-maven-plugin:prepare-agent”参数即可。 示例:

mvn clean test org.jacoco:jacoco-maven-plugin:0.7.3.201502191951:prepare-agent install -Dmaven.test.failure.ignore=true

在pom文件中添加jacoco插件

具体的配置方法如下:

1.添加依賴


    org.jacoco
    jacoco-maven-plugin
    0.8.3

2.配置plugins

      <plugin>
        <groupId>org.jacocogroupId>
        <artifactId>jacoco-maven-pluginartifactId>
        <version>0.8.3version>
        <configuration>
          <includes>
            <include>com/**/*include>
          includes>
        configuration>
        <executions>
          <execution>
            <id>pre-testid>
            <goals>
              <goal>prepare-agentgoal>
            goals>
          execution>
          <execution>
            <id>post-testid>
            <phase>testphase>
            <goals>
              <goal>reportgoal>
            goals>
          execution>
        executions>
      plugin>

其中包含(includes)或排除(excludes)字段的值应该是相对于目录/ classes /的编译类的类路径(而不是包名),使用标准通配符语法:

*   Match zero or more characters
**  Match zero or more directories
?   Match a single character

你也可以这样排除一个包和它的所有子包/子包:

com/src/**/*

这将排除某些包装中的每个课程,以及任何孩子。例如,com.src.child也不会包含在报表中。

也可以在pom中指定筛选规则:

      <plugin>
        <groupId>org.jacocogroupId>
        <artifactId>jacoco-maven-pluginartifactId>
        <version>${jacoco.version}version>
        <configuration>
          <includes>
            <include>com/src/**/*include>
          includes>
          
          <rules>
            <rule implementation="org.jacoco.maven.RuleConfiguration">
              <element>BUNDLEelement>
              <limits>  
                
                <limit implementation="org.jacoco.report.check.Limit">
                  <counter>METHODcounter>
                  <value>COVEREDRATIOvalue>
                  <minimum>0.50minimum>
                limit>
                
                <limit implementation="org.jacoco.report.check.Limit">
                  <counter>BRANCHcounter>
                  <value>COVEREDRATIOvalue>
                  <minimum>0.50minimum>
                limit>
                
                <limit implementation="org.jacoco.report.check.Limit">
                  <counter>CLASScounter>
                  <value>MISSEDCOUNTvalue>
                  <maximum>0maximum>
                limit>
              limits>
            rule>
          rules>
        configuration>
        <executions>
          <execution>
            <id>pre-testid>
            <goals>
              <goal>prepare-agentgoal>
            goals>
          execution>
          <execution>
            <id>post-testid>
            <phase>testphase>
            <goals>
              <goal>reportgoal>
            goals>
          execution>
        executions>
      plugin>

此时运行mvn test生成index.html(即覆盖率报告)位置在:

jacoco统计覆盖率最佳实践_第3张图片

也可以指定输出目录:

<execution>
    <id>post-unit-testid>
    <phase>testphase>
    <goals>
        <goal>reportgoal>
    goals>
    <configuration>
        <dataFile>target/jacoco.execdataFile>
        <outputDirectory>target/jacoco-utoutputDirectory>
    configuration>
execution>

功能测试on-the-fly模式

对应的是jacoco的on-the-fly模式。

JVM通过 -javaagent参数指定jar文件启动代理程序,代理程序在ClassLoader装载一个class前判断是否修改class文件,并将探针插入class文件,探针不改变原有方法的行为,只是记录是否已经执行。

启jacoco命令:

打开cmd,cd到示例代码的target目录,执行如下命令:

基本语法

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

示例

java -
javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar
demo.jar

关键参数说明:

  • destfile 执行数据的输出文件的路径。
  • append 如果设置为true并且执行数据文件已经存在,则覆盖率数据将附加到现有文件中。如果设置为false,则将替换现有的执行数据文件。
  • includes 应包含在执行分析中的类名列表,意思就是要收集哪些类的覆盖率信息。列表条目由冒号(:)分隔,并且可以使用通配符(*和?)。除了性能优化或技术拐角情况外,通常不需要此选项。
  • excludes 应从执行分析中排除的类名列表。列表条目由冒号(:)分隔,并且可以使用通配符(*和?)。除了性能优化或技术拐角情况外,通常不需要此选项。如果您想从报告中排除类,请相应地配置相应的报告生成工具。与includes相反。
  • exclclassloader 应从执行分析中排除的类加载器名称的列表。列表条目由冒号(:)分隔,并且可以使用通配符(*和?)。如果特殊框架与JaCoCo代码插入冲突,特别是无法访问Java运行时类的类加载器,则可能需要此选项。
  • output 有 4 个值,分别是 file、tcpserver、tcpclient、mbean,默认是 file。使用 file 的方式只有在停掉应用服务的时候才能产生覆盖率文件,而使用 tcpserver 的方式可以在不停止应用服务的情况下下载覆盖率文件,后面会介绍如何使用 dump 方法来得到覆盖率文件。
    • file:在JVM终止时,执行数据被写入本地文件。
    • tcpserver:外部工具可以连接到JVM,并通过套接字连接检索执行数据。可以在虚拟机退出时进行可选的执行数据重置和执行数据转储。
    • tcpclient:在启动时,JaCoCo代理连接到给定的TCP端点。根据请求将执行数据写入套接字连接。可以在虚拟机退出时进行可选的执行数据重置和执行数据转储。
    • none:不做任何产出。
  • address 是 IP 地址,当输出方法为tcpserver时要绑定到的IP地址或主机名,或者当输出方法是tcpclient时要连接到的IP或主机名。在tcpserver模式中,值“*”会使代理接受任何本地地址上的连接。
  • port 是端口(输出方法为tcpserver时要绑定到的端口,或输出方法为tcpclient时要连接到的端口。在tcpserver模式下,端口必须可用,这意味着如果多个JaCoCo代理应在同一台计算机上运行,则必须指定不同的端口。
  • dumponexit 如果设置为真覆盖率,则将在VM关闭时写入数据。只有在指定了文件或输出为tcpserver/tcpclient并且VM终止时连接打开时,才能写入转储。
  • classdumpdir相对于代理看到的所有类文件转储到的工作目录的位置。这对于调试或动态创建的类(例如使用脚本引擎时)非常有用。

dump - 生成exec文件

基于参数

java -jar jacococli.jar dump [--address 
] --destfile [--help] [--port ] [--quiet] [--reset] [--retry ]
Option Description
--address
要连接的主机名或ip地址(默认本地主机)
--destfile 要将执行数据写入的文件
--help show help
--port 要连接的端口(默认值为6300)
--quiet 禁止stdout上的所有输出
--reset 转储后重置测试目标上的执行数据
--retry 重试次数(默认为10)
java -jar jacococli.jar dump --address 127.0.0.1 --port 6300 --destfile ./jacoco-demo.exec

report - 生成报告

基本参数

java -jar jacococli.jar report [ ...] --classfiles  [--csv ] [--encoding ] [--help] [--html ] [--name ] [--quiet] [--sourcefiles ] [--tabwith ] [--xml ]
java -jar jacococli.jar report jacoco-demo.exec --classfiles ../../../target/classes --sourcefiles ../../main/java --html http-report --xml report.xml --encoding=utf-8

注意 jacococli.jar 是放到了test下的resource下上述命令。

Option Description
要读取的JaCoCo*.exec文件列表
--classfiles Java类文件的位置
--csv CSV报告的输出文件
--encoding 源文件编码(默认使用平台编码)
--help show help
--html HTML报表的输出目录
--name 用于此报告的名称
--quiet 禁止stdout上的所有输出
--sourcefiles 源文件的位置 ,既源码位置

instrument - Java类文件和JAR文件的离线检测。

基本参数

java -jar jacococli.jar instrument [ ...] --dest  [--help] [--quiet]

Option Description
源文件的位置 ,既源码位置
--dest 将插入指令的Java类写入的路径
--help show help
--quiet suppress all output on stdout

merge - 将多个exec文件合并到一个新的exec文件中。

基本参数

java -jar jacococli.jar merge [ ...] --destfile  [--help] [--quiet]

Option Description
要读取的JaCoCo*.exec文件列表
--destfile 将合并的执行数据写入的文件
--help show help
--quiet suppress all output on stdout

这个merge参数是讲多个exec整合为一个文件,据我使用经验来说有两方面用途吧,一是讲多个模块的覆盖率信息统计到一起,二是将n次相同模块的测试结果整合统计。都很有用。

示例

java -jar jacococli.jar merge jacoco-demo.exec jacoco-demo1.exec --destfile ./demo.exec

classinfo - 在提供的位置打印有关Java类文件的信息。

java -jar jacococli.jar classinfo [ ...] [--help] [--quiet] [--verbose]
Option Description
Java类文件的位置
--help show help
--quiet suppress all output on stdout
--verbose show method and line number details

execinfo - 以可读格式打印exec文件内容

java -jar jacococli.jar execinfo [ ...] [--help] [--quiet]
Option Description
list of JaCoCo *.exec files to read
--help show help
--quiet suppress all output on stdout

你可能感兴趣的:(#,jacoco,java积累,工具使用,java,开发语言,jacoco)