Jacoco基本概念

Jacoco是一个开源的覆盖率工具,针对java语言。

一、覆盖率计数器

1. 行覆盖

所有类文件均携带debug信息编译,则每行的覆盖率可计算。当至少一个指令被指定到源码行且已执行时,该源码行被认为已执行。
**全部未覆盖:该行中指令均未执行,红色标志
**部分覆盖:该行中部分指令执行,黄色标志
**全覆盖:该行中所有指令已执行,绿色标志

2. 类覆盖

当类中至少有一个方法已执行,则该类被认为已执行。Jacoco中认为构造函数和静态初始化方法也当作被执行过的方法。Java接口类型若包含静态初始化方法,这种接口也被认为是可执行的类。

3. 方法覆盖

每个非抽象方法至少包含一个指令。当至少一个指令被执行,该方法被认为已执行。由于Jacoco基于字节码级别的,构造函数和静态初始化方法也被当作方法计算。其中有些方法,可能无法直接对应到源码中,比如默认构造器或常量的初始化命令。

4. 分支覆盖

Jacoco为if和switch语句计算分支覆盖率。这个指标计算一个方法中的分支总数,并决定已执行和未执行的分支的数量。分支覆盖率在class文件中缺少debug信息时也可使用。异常处理不在分支覆盖的统计范围内。
**全部未覆盖:所有分支均未执行,红色标志
**部分覆盖:只有部分分支被执行,黄色标志
**全覆盖:所有分支均已执行,绿色标志

5. 指令覆盖

Jacoco计数的最小单元是Java字节码指令,它为执行/未执行代码提供了大量的信息。这个指标完全独立于源格式,在类文件中缺少debug信息时也可以使用。

6. 圈复杂度

Jacoco对每个非抽象方法计算圈复杂度,总结类、包、组的复杂性。
圈复杂度:在(线性)组合中,计算在一个方法里面所有可能路径的最小数目。所以复杂度可以作为度量单元测试是否有完全覆盖所有场景的一个依据。在没有debug信息的时候也可以使用。
**圈复杂度V(G)是基于方法的控制流图的有向图表示:V(G) = E - N + 2
**E是边界数量,N是节点数量。
**Jacoco基于下面方程来计算复杂度,B是分支数量,D是决策点数量:
**V(G) = B - D + 1
基于每个分支的被覆盖情况,Jacoco也未每个方法计算覆盖和缺失的复杂度。缺失复杂度同样表示测试案例没有完全覆盖到这个模块。注意Jacoco不将异常处理作为分支,try/catch块也同样不增加复杂度。

二、Jacoco原理

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

1. on-the-fly模式

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

2. offline模式

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

on-the-fly和offline对比

on-the-fly更方便简单,无需提前插桩,无需考虑classpath设置问题。
以下情况不适合使用on-the-fly模式:
(1)不支持javaagent
(2)无法设置JVM参数
(3)字节码需要被转换成其他虚拟机
(4)动态修改字节码过程和其他agent冲突
(5)无法自定义用户加载类

Java方法的控制流分析

官方文档在这里:https://www.jacoco.org/jacoco/trunk/doc/flow.html

1. 探针插入策略

探针可以在现有指令之间插入附加指令,他们不改变已有方法行为,只是去记录是否已经执行。可以认为探针放置在控制流图的边缘上,理论上讲,我们可以在控制流图的每个边缘插入一个探针,但这样会增加类文件大小,降低执行速度。事实上,我们每个方法只需要一些探针,具体取决于方法的控制流程。
如果已经执行了探测,我们知道已经访问了相应的边缘,从这个边缘我们可以得出其他前面的节点和边:
(1)如果访问了边,我们知道该边的源节点已经被执行。
(2)如果节点已经被执行且节点是一个边缘的目标节点,则我们知道已经访问了该边。

image.png
上述探针插入策略没有考虑到隐式异常,如果两个探针之间的控制流被未使用throw的语句显示创建的异常终端,则其间的所有指令都被视为未覆盖。因此,只要后续行包含至少一个方法调用,Jacoco就会在两行的指令间添加额外的探测。该方法仅使用于有debug信息的编译的类文件。且不考虑除方法调用之外的其他指令的隐式异常。

2. 探针的实现

探针需要满足如下几点要求:
(1)记录执行
(2)识别不同的探针
(3)线程安全
(4)对应用程序无影响
(5)最小的运行时开销
Jacoco给每个类一个boolean[]数组实例,每个探针对应该数组中的一个条目。无论何时执行,都用下面4条字节码指令将条目设置为true。

ALOAD    probearray
xPUSH    probeid
ICONST_1
BASTORE

三、Jacoco的使用方式

  1. 不详细介绍了=》ant
  2. 不详细介绍了=》maven
    3.不详细介绍了=》offline
  3. Java agent
    Jacoco的使用分为三部分,第一部分是注入并采集,第二部分是导出,第三部分是生成报告,三部分可以分开执行。

(1)首先在被测程序的启动命令行中加上-javaagent选项,指定jacocoagent.jar作为代理程序。

Jacoco agent搜集执行信息并且在请求或者JVM退出的时候导出数据。有三种不同的导出数据模式:

  • 文件系统:JVM停止时,数据被导出到本地文件
  • TCP socket Server:监听端口连接,通过socket连接获取到执行数据。在VM退出时,可选择进行数据重置和数据导出。
  • TCP socket Client:启动时,Jacoco agent连接到一个给定的TCP端,请求时执行数据写到socket,在VM退出时,可选择进行数据重置和数据导出。
  -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
image.png

(2)导出数据,假如指定导出模式为tcpserver,那么我们需要启动一个client来请求覆盖率文件数据。

  • 代码导出
    Jacoco给出的example示例如下:
/*******************************************************************************
 * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.examples;

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

import org.jacoco.core.data.ExecutionDataWriter;
import org.jacoco.core.runtime.RemoteControlReader;
import org.jacoco.core.runtime.RemoteControlWriter;

/**
 * This example connects to a coverage agent that run in output mode
 * <code>tcpserver</code> and requests execution data. The collected data is
 * dumped to a local file.
 */
public final class ExecutionDataClient {

    private static final String DESTFILE = "jacoco-client.exec";

    private static final String ADDRESS = "localhost";

    private static final int PORT = 6300;

    /**
     * Starts the execution data request.
     * 
     * @param args
     * @throws IOException
     */
    public static void main(final String[] args) throws IOException {
        final FileOutputStream localFile = new FileOutputStream(DESTFILE);
        final ExecutionDataWriter localWriter = new ExecutionDataWriter(
                localFile);

        // Open a socket to the coverage agent:
        final Socket socket = new Socket(InetAddress.getByName(ADDRESS), PORT);
        final RemoteControlWriter writer = new RemoteControlWriter(
                socket.getOutputStream());
        final RemoteControlReader reader = new RemoteControlReader(
                socket.getInputStream());
        reader.setSessionInfoVisitor(localWriter);
        reader.setExecutionDataVisitor(localWriter);

        // Send a dump command and read the response:
        writer.visitDumpCommand(true, false);
        if (!reader.read()) {
            throw new IOException("Socket closed unexpectedly.");
        }

        socket.close();
        localFile.close();
    }

    private ExecutionDataClient() {
    }
}
  • 命令行导出
    image.png

(3)到此,已经生成了exec文件,那我们的报告呢?

  • 代码生成报告
    官方示例如下:
/*******************************************************************************
 * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Brock Janiczak - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.examples;

import java.io.File;
import java.io.IOException;

import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IBundleCoverage;
import org.jacoco.core.tools.ExecFileLoader;
import org.jacoco.report.DirectorySourceFileLocator;
import org.jacoco.report.FileMultiReportOutput;
import org.jacoco.report.IReportVisitor;
import org.jacoco.report.html.HTMLFormatter;

/**
 * This example creates a HTML report for eclipse like projects based on a
 * single execution data store called jacoco.exec. The report contains no
 * grouping information.
 * 
 * The class files under test must be compiled with debug information, otherwise
 * source highlighting will not work.
 */
public class ReportGenerator {

    private final String title;

    private final File executionDataFile;
    private final File classesDirectory;
    private final File sourceDirectory;
    private final File reportDirectory;

    private ExecFileLoader execFileLoader;

    /**
     * Create a new generator based for the given project.
     * 
     * @param projectDirectory
     */
    public ReportGenerator(final File projectDirectory) {
        this.title = projectDirectory.getName();
        this.executionDataFile = new File(projectDirectory, "jacoco.exec");
        this.classesDirectory = new File(projectDirectory, "bin");
        this.sourceDirectory = new File(projectDirectory, "src");
        this.reportDirectory = new File(projectDirectory, "coveragereport");
    }

    /**
     * Create the report.
     * 
     * @throws IOException
     */
    public void create() throws IOException {

        // Read the jacoco.exec file. Multiple data files could be merged
        // at this point
        loadExecutionData();

        // Run the structure analyzer on a single class folder to build up
        // the coverage model. The process would be similar if your classes
        // were in a jar file. Typically you would create a bundle for each
        // class folder and each jar you want in your report. If you have
        // more than one bundle you will need to add a grouping node to your
        // report
        final IBundleCoverage bundleCoverage = analyzeStructure();

        createReport(bundleCoverage);

    }

    private void createReport(final IBundleCoverage bundleCoverage)
            throws IOException {

        // Create a concrete report visitor based on some supplied
        // configuration. In this case we use the defaults
        final HTMLFormatter htmlFormatter = new HTMLFormatter();
        final IReportVisitor visitor = htmlFormatter
                .createVisitor(new FileMultiReportOutput(reportDirectory));

        // Initialize the report with all of the execution and session
        // information. At this point the report doesn't know about the
        // structure of the report being created
        visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),
                execFileLoader.getExecutionDataStore().getContents());

        // Populate the report structure with the bundle coverage information.
        // Call visitGroup if you need groups in your report.
        visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(
                sourceDirectory, "utf-8", 4));

        // Signal end of structure information to allow report to write all
        // information out
        visitor.visitEnd();

    }

    private void loadExecutionData() throws IOException {
        execFileLoader = new ExecFileLoader();
        execFileLoader.load(executionDataFile);
    }

    private IBundleCoverage analyzeStructure() throws IOException {
        final CoverageBuilder coverageBuilder = new CoverageBuilder();
        final Analyzer analyzer = new Analyzer(
                execFileLoader.getExecutionDataStore(), coverageBuilder);

        analyzer.analyzeAll(classesDirectory);

        return coverageBuilder.getBundle(title);
    }

    /**
     * Starts the report generation process
     * 
     * @param args
     *            Arguments to the application. This will be the location of the
     *            eclipse projects that will be used to generate reports for
     * @throws IOException
     */
    public static void main(final String[] args) throws IOException {
        for (int i = 0; i < args.length; i++) {
            final ReportGenerator generator = new ReportGenerator(new File(
                    args[i]));
            generator.create();
        }
    }

}
  • 命令行生成报告
    image.png

到此,我们就学会了on-the-fly模式的Jacoco使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容

  • 测试覆盖率 测试覆盖率是对测试完全程度的评测。测试覆盖率是由测试需求和测试用例的覆盖或已执行代码的覆盖的表示结果。...
    android老男孩阅读 45,781评论 2 15
  • tags: Java 前阵子使用 Jacoco 进行代码覆盖率测试,由于项目特殊遇到了不少坑,网上搜到的教程感觉也...
    原来一起都是那么de美好阅读 8,998评论 0 1
  • tags: Java 前阵子使用 Jacoco 进行代码覆盖率测试,由于项目特殊遇到了不少坑,网上搜到的教程感觉也...
    LensAclrtn阅读 30,381评论 2 7
  • Java Jacoco Ant Maven 针对 Jacoco[http://www.eclemma.org/ja...
    纳爱斯阅读 53,393评论 64 52
  • 贺变丽 焦点解决中级十期 洛阳嵩县 坚持分享第201天 2018--12--29 这几天,我一直在读...
    hebl阅读 551评论 0 3