Jacoco基本概念

Jacoco是一個開源的覆蓋率工具糯崎,針對java語言队询。

一往踢、覆蓋率計(jì)數(shù)器

1. 行覆蓋

所有類文件均攜帶debug信息編譯慰技,則每行的覆蓋率可計(jì)算椭盏。當(dāng)至少一個指令被指定到源碼行且已執(zhí)行時(shí),該源碼行被認(rèn)為已執(zhí)行吻商。
**全部未覆蓋:該行中指令均未執(zhí)行掏颊,紅色標(biāo)志
**部分覆蓋:該行中部分指令執(zhí)行,黃色標(biāo)志
**全覆蓋:該行中所有指令已執(zhí)行艾帐,綠色標(biāo)志

2. 類覆蓋

當(dāng)類中至少有一個方法已執(zhí)行乌叶,則該類被認(rèn)為已執(zhí)行。Jacoco中認(rèn)為構(gòu)造函數(shù)和靜態(tài)初始化方法也當(dāng)作被執(zhí)行過的方法柒爸。Java接口類型若包含靜態(tài)初始化方法枉昏,這種接口也被認(rèn)為是可執(zhí)行的類。

3. 方法覆蓋

每個非抽象方法至少包含一個指令揍鸟。當(dāng)至少一個指令被執(zhí)行兄裂,該方法被認(rèn)為已執(zhí)行。由于Jacoco基于字節(jié)碼級別的阳藻,構(gòu)造函數(shù)和靜態(tài)初始化方法也被當(dāng)作方法計(jì)算晰奖。其中有些方法,可能無法直接對應(yīng)到源碼中腥泥,比如默認(rèn)構(gòu)造器或常量的初始化命令匾南。

4. 分支覆蓋

Jacoco為if和switch語句計(jì)算分支覆蓋率。這個指標(biāo)計(jì)算一個方法中的分支總數(shù)蛔外,并決定已執(zhí)行和未執(zhí)行的分支的數(shù)量蛆楞。分支覆蓋率在class文件中缺少debug信息時(shí)也可使用溯乒。異常處理不在分支覆蓋的統(tǒng)計(jì)范圍內(nèi)。
**全部未覆蓋:所有分支均未執(zhí)行豹爹,紅色標(biāo)志
**部分覆蓋:只有部分分支被執(zhí)行裆悄,黃色標(biāo)志
**全覆蓋:所有分支均已執(zhí)行,綠色標(biāo)志

5. 指令覆蓋

Jacoco計(jì)數(shù)的最小單元是Java字節(jié)碼指令臂聋,它為執(zhí)行/未執(zhí)行代碼提供了大量的信息光稼。這個指標(biāo)完全獨(dú)立于源格式,在類文件中缺少debug信息時(shí)也可以使用孩等。

6. 圈復(fù)雜度

Jacoco對每個非抽象方法計(jì)算圈復(fù)雜度艾君,總結(jié)類、包肄方、組的復(fù)雜性冰垄。
圈復(fù)雜度:在(線性)組合中,計(jì)算在一個方法里面所有可能路徑的最小數(shù)目权她。所以復(fù)雜度可以作為度量單元測試是否有完全覆蓋所有場景的一個依據(jù)虹茶。在沒有debug信息的時(shí)候也可以使用。
**圈復(fù)雜度V(G)是基于方法的控制流圖的有向圖表示:V(G) = E - N + 2
**E是邊界數(shù)量伴奥,N是節(jié)點(diǎn)數(shù)量写烤。
**Jacoco基于下面方程來計(jì)算復(fù)雜度翼闽,B是分支數(shù)量拾徙,D是決策點(diǎn)數(shù)量:
**V(G) = B - D + 1
基于每個分支的被覆蓋情況,Jacoco也未每個方法計(jì)算覆蓋和缺失的復(fù)雜度感局。缺失復(fù)雜度同樣表示測試案例沒有完全覆蓋到這個模塊尼啡。注意Jacoco不將異常處理作為分支,try/catch塊也同樣不增加復(fù)雜度询微。

二崖瞭、Jacoco原理

Jacoco使用插樁的方式來記錄覆蓋率數(shù)據(jù),是通過一個probe探針來注入撑毛。
插樁模式有兩種:

1. on-the-fly模式

JVM通過 -javaagent參數(shù)指定jar文件啟動代理程序书聚,代理程序在ClassLoader裝載一個class前判斷是否修改class文件,并將探針插入class文件藻雌,探針不改變原有方法的行為雌续,只是記錄是否已經(jīng)執(zhí)行。

2. offline模式

在測試之前先對文件進(jìn)行插樁胯杭,生成插過樁的class或jar包驯杜,測試插過樁的class和jar包,生成覆蓋率信息到文件做个,最后統(tǒng)一處理鸽心,生成報(bào)告滚局。

on-the-fly和offline對比

on-the-fly更方便簡單,無需提前插樁顽频,無需考慮classpath設(shè)置問題藤肢。
以下情況不適合使用on-the-fly模式:
(1)不支持javaagent
(2)無法設(shè)置JVM參數(shù)
(3)字節(jié)碼需要被轉(zhuǎn)換成其他虛擬機(jī)
(4)動態(tài)修改字節(jié)碼過程和其他agent沖突
(5)無法自定義用戶加載類

Java方法的控制流分析

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

1. 探針插入策略

探針可以在現(xiàn)有指令之間插入附加指令,他們不改變已有方法行為冲九,只是去記錄是否已經(jīng)執(zhí)行谤草。可以認(rèn)為探針放置在控制流圖的邊緣上莺奸,理論上講丑孩,我們可以在控制流圖的每個邊緣插入一個探針,但這樣會增加類文件大小灭贷,降低執(zhí)行速度温学。事實(shí)上,我們每個方法只需要一些探針甚疟,具體取決于方法的控制流程仗岖。
如果已經(jīng)執(zhí)行了探測,我們知道已經(jīng)訪問了相應(yīng)的邊緣览妖,從這個邊緣我們可以得出其他前面的節(jié)點(diǎn)和邊:
(1)如果訪問了邊轧拄,我們知道該邊的源節(jié)點(diǎn)已經(jīng)被執(zhí)行。
(2)如果節(jié)點(diǎn)已經(jīng)被執(zhí)行且節(jié)點(diǎn)是一個邊緣的目標(biāo)節(jié)點(diǎn)讽膏,則我們知道已經(jīng)訪問了該邊檩电。

image.png
上述探針插入策略沒有考慮到隱式異常,如果兩個探針之間的控制流被未使用throw的語句顯示創(chuàng)建的異常終端府树,則其間的所有指令都被視為未覆蓋俐末。因此,只要后續(xù)行包含至少一個方法調(diào)用奄侠,Jacoco就會在兩行的指令間添加額外的探測卓箫。該方法僅使用于有debug信息的編譯的類文件。且不考慮除方法調(diào)用之外的其他指令的隱式異常垄潮。

2. 探針的實(shí)現(xiàn)

探針需要滿足如下幾點(diǎn)要求:
(1)記錄執(zhí)行
(2)識別不同的探針
(3)線程安全
(4)對應(yīng)用程序無影響
(5)最小的運(yùn)行時(shí)開銷
Jacoco給每個類一個boolean[]數(shù)組實(shí)例烹卒,每個探針對應(yīng)該數(shù)組中的一個條目。無論何時(shí)執(zhí)行弯洗,都用下面4條字節(jié)碼指令將條目設(shè)置為true旅急。

ALOAD    probearray
xPUSH    probeid
ICONST_1
BASTORE

三、Jacoco的使用方式

  1. 不詳細(xì)介紹了=》ant
  2. 不詳細(xì)介紹了=》maven
    3.不詳細(xì)介紹了=》offline
  3. Java agent
    Jacoco的使用分為三部分涂召,第一部分是注入并采集坠非,第二部分是導(dǎo)出,第三部分是生成報(bào)告果正,三部分可以分開執(zhí)行炎码。

(1)首先在被測程序的啟動命令行中加上-javaagent選項(xiàng)盟迟,指定jacocoagent.jar作為代理程序。

Jacoco agent搜集執(zhí)行信息并且在請求或者JVM退出的時(shí)候?qū)С鰯?shù)據(jù)潦闲。有三種不同的導(dǎo)出數(shù)據(jù)模式:

  • 文件系統(tǒng):JVM停止時(shí)攒菠,數(shù)據(jù)被導(dǎo)出到本地文件
  • TCP socket Server:監(jiān)聽端口連接,通過socket連接獲取到執(zhí)行數(shù)據(jù)歉闰。在VM退出時(shí)辖众,可選擇進(jìn)行數(shù)據(jù)重置和數(shù)據(jù)導(dǎo)出。
  • TCP socket Client:啟動時(shí)和敬,Jacoco agent連接到一個給定的TCP端凹炸,請求時(shí)執(zhí)行數(shù)據(jù)寫到socket,在VM退出時(shí)昼弟,可選擇進(jìn)行數(shù)據(jù)重置和數(shù)據(jù)導(dǎo)出啤它。
  -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2]
image.png

(2)導(dǎo)出數(shù)據(jù),假如指定導(dǎo)出模式為tcpserver舱痘,那么我們需要啟動一個client來請求覆蓋率文件數(shù)據(jù)变骡。

  • 代碼導(dǎo)出
    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() {
    }
}
  • 命令行導(dǎo)出
    image.png

(3)到此,已經(jīng)生成了exec文件芭逝,那我們的報(bào)告呢塌碌?

  • 代碼生成報(bào)告
    官方示例如下:
/*******************************************************************************
 * 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();
        }
    }

}
  • 命令行生成報(bào)告
    image.png

到此,我們就學(xué)會了on-the-fly模式的Jacoco使用旬盯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末台妆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓢捉,更是在濱河造成了極大的恐慌频丘,老刑警劉巖办成,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泡态,死亡現(xiàn)場離奇詭異,居然都是意外死亡迂卢,警方通過查閱死者的電腦和手機(jī)某弦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來而克,“玉大人靶壮,你說我怎么就攤上這事≡逼迹” “怎么了腾降?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碎绎。 經(jīng)常有香客問我螃壤,道長抗果,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任奸晴,我火速辦了婚禮冤馏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寄啼。我一直安慰自己逮光,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布墩划。 她就那樣靜靜地躺著涕刚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乙帮。 梳的紋絲不亂的頭發(fā)上副女,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音蚣旱,去河邊找鬼碑幅。 笑死,一個胖子當(dāng)著我的面吹牛塞绿,可吹牛的內(nèi)容都是我干的沟涨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼异吻,長吁一口氣:“原來是場噩夢啊……” “哼裹赴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诀浪,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤棋返,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雷猪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體睛竣,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年求摇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了射沟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡与境,死狀恐怖验夯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情摔刁,我是刑警寧澤挥转,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響绑谣,放射性物質(zhì)發(fā)生泄漏准潭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一域仇、第九天 我趴在偏房一處隱蔽的房頂上張望刑然。 院中可真熱鬧,春花似錦暇务、人聲如沸泼掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽择镇。三九已至,卻和暖如春括改,著一層夾襖步出監(jiān)牢的瞬間腻豌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工嘱能, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吝梅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓惹骂,卻偏偏與公主長得像苏携,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子对粪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 測試覆蓋率 測試覆蓋率是對測試完全程度的評測右冻。測試覆蓋率是由測試需求和測試用例的覆蓋或已執(zhí)行代碼的覆蓋的表示結(jié)果。...
    android老男孩閱讀 45,856評論 2 15
  • tags: Java 前陣子使用 Jacoco 進(jìn)行代碼覆蓋率測試著拭,由于項(xiàng)目特殊遇到了不少坑纱扭,網(wǎng)上搜到的教程感覺也...
    原來一起都是那么de美好閱讀 9,179評論 0 1
  • tags: Java 前陣子使用 Jacoco 進(jìn)行代碼覆蓋率測試,由于項(xiàng)目特殊遇到了不少坑儡遮,網(wǎng)上搜到的教程感覺也...
    LensAclrtn閱讀 30,578評論 2 7
  • Java Jacoco Ant Maven 針對 Jacoco[http://www.eclemma.org/ja...
    納愛斯閱讀 53,498評論 64 52
  • 賀變麗 焦點(diǎn)解決中級十期 洛陽嵩縣 堅(jiān)持分享第201天 2018--12--29 這幾天乳蛾,我一直在讀...
    hebl閱讀 556評論 0 3