成为我能成为的人 [定制触发条件] jacoco 统计 Android 代码覆盖率

zailushang · 2019年01月01日 · 最后由 rockYSYR123 回复于 2019年01月18日 · 345 次阅读

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
}

如图所示:

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 3 条回复 时间 点赞

卡在app:transformClassesWithDexForDebug 真的卡了好久好久,之前一直以为是因为集成的工程太复杂导致,请问到底是什么原因呢?
我们是把minifyEnabled设置成true就可以成功了

剪烛 回复

我这边猜测可能是内存资源紧张导致的速度慢,所以打开代码压缩和资源压缩(开发也这么认为),后期可以深入研究一下

你好 麻烦加一下qq 有指教 谢谢 371844911

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册