[定制触发条件] jacoco 统计 Android 代码覆盖率

1、前言

之前已经写过一篇关于 [instrument 方式] Jacoco 统计 Android 端手工测试覆盖率,但是在实际使用过程中,自由度不够。
针对不同的测试类型,可能需要不同的写覆盖率文件触发方式。所以才有了本篇:修改源码的方式,定制化写代码覆盖率文件的触发条件。

2、概述

看下文之前,首先考虑一个问题:把大象放进冰箱,一共分几步?
......
同样的,用Jacoco统计Android代码覆盖率,一共分6步:

  1. 编写生成覆盖率文件coverage.ec的类和方法
  2. build.gradle(app)新增jacoco插件
  3. 打开覆盖率统计开关
  4. 覆盖率生成条件监听
  5. 安装debug版本
  6. 服务器生成jacoco报告

3、具体步骤

3.1 编写生成覆盖率文件coverage.ec的类和方法

src目录下,新建一个jacocotest package,放入JacocoUtils.java测试类

代码见:

package com.keniu.security.main.jacocotest;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class JacocoUtils {
    static String TAG = "JacocoUtils";

    //ec文件的路径
    private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/cleanmaster_coverage.ec";

    /**
     * 生成ec文件
     *
     * @param isNew 是否重新创建ec文件
     */
    public static void generateEcFile(boolean isNew) {
        String currentTimeStr = String.valueOf(System.currentTimeMillis());
//        DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/cleanmaster_coverage" + currentTimeStr + ".ec";
        Log.d(TAG, "生成覆盖率文件: " + DEFAULT_COVERAGE_FILE_PATH);
        OutputStream out = null;
        File mCoverageFilePath = new File(DEFAULT_COVERAGE_FILE_PATH);

        //create and delete coverage.ec
        try {
            if (isNew && mCoverageFilePath.exists()) {
                Log.d(TAG, "JacocoUtils_generateEcFile: 清除旧的ec文件");
//                mCoverageFilePath.delete();
            }
            if (!mCoverageFilePath.exists()) {
                mCoverageFilePath.createNewFile();
            }
            out = new FileOutputStream(mCoverageFilePath.getPath(), true);

            //反射:获取org.jacoco.agent.rt.IAgent
            Object agent = Class.forName("org.jacoco.agent.rt.RT"   )
                    .getMethod("getAgent")
                    .invoke(null);

            //反射:getExecutionData(boolean reset),获取当前执行数据,以jacoco二进制格式转储当前执行数据
            // getExecutionData(boolean reset),reset如果为true,则之后清除当前执行数据
            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false))  ;

        } catch (Exception e) {
            Log.e(TAG, "generateEcFile: " + e.getMessage());
        } finally {
            if (out == null)
                return;
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

3.2 build.gradle(app)新增jacoco插件

apply plugin: 'jacoco'

jacoco {
    toolVersion = '0.7.4+'
}

如图所示:

3.3 打开覆盖率统计开关

说明:也可选择release版本打开覆盖率开关。

buildTypes {
    debug {
        /**打开覆盖率统计开关*/
 testCoverageEnabled = true
    }
}

如图所示:

3.4 覆盖率生成条件监听

触发条件可以根据覆盖率统计场景,选择合适的一个。

(1)可新建线程定时触发

线程代码见:

package com.keniu.security.main.jacocotest.handler;

import android.os.Handler;
import android.os.Message;

import com.keniu.security.main.jacocotest.JacocoUtils;

public class MyThread implements Runnable {
    Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            // 要做的事情
            JacocoUtils.generateEcFile(true);
        }
    };

    boolean jacocoBoolean = true;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (jacocoBoolean) {
            try {
                Thread.sleep(10000);// 线程暂停10秒,单位毫秒
                Message message = new Message();
                message.what = 1;
                handler.sendMessage(message);// 发送消息
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public void end() {
        jacocoBoolean = false;
    }
}

在MainActivity中新建线程并启动:

MyThread jacocoThread = new MyThread();
@Override
protected void onCreate(Bundle savedInstanceState) {
    ......
    //jacoco定时任务开始
 new Thread(jacocoThread).start();
   ......      
}

(2)加在监听设备按键的地方,如果连续2次点击设备back键,app已置于后台,则调用生成覆盖率方法。

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        ....
        JacocoUtils.generateEcFile(true);
    }
}

3.5 安装debug版本

build / Rebuild Project

安装app-debug.apk至手机

3.6 服务器生成jacoco报告

(1)在build.gradle(project)新增jacocoTestReport

def coverageSourceDirs = [
        './src/'
]

task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
 description = "Generate Jacoco coverage reports after running tests."
 reports {
        xml.enabled = true
 html.enabled = true
 }
    classDirectories = fileTree(
            dir: './build/intermediates/classes/',
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
 ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files("$buildDir/outputs/cleanmaster_coverage.ec")

    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }
}

2)上传ec文件并生成报告

然后设备上传ec文件到Android工程的$buildDir/outputs/code-coverage/connected目录下,并依次执行

gradle createDebugCoverageReport
gradle jacocoTestReport

3.7 可能遇到问题

(1)rebuild Project时卡在app:transformClassesWithDexForDebug

解决方法:

按照如下方式设置即可:

debug {
    buildConfigField "boolean", "FORTEST", "false"
 // 启动代码压缩
 minifyEnabled true
 // 启用资源压缩
 shrinkResources true
 signingConfig signingConfigs.debug
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'kbrowser.pro'
 /**打开覆盖率统计开关*/
 testCoverageEnabled = true
}

如图所示:

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

推荐阅读更多精彩内容