[定制触发条件] 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+'
}

如图所示:

image

3.3 打开覆盖率统计开关

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

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

如图所示:

image

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
}

如图所示:

image

你可能感兴趣的:([定制触发条件] jacoco 统计 Android 代码覆盖率)