1、前言
之前已经写过一篇关于 [instrument 方式] Jacoco 统计 Android 端手工测试覆盖率,但是在实际使用过程中,自由度不够。
针对不同的测试类型,可能需要不同的写覆盖率文件触发方式。所以才有了本篇:修改源码的方式,定制化写代码覆盖率文件的触发条件。
2、概述
看下文之前,首先考虑一个问题:把大象放进冰箱,一共分几步?
......
同样的,用 Jacoco 统计 Android 代码覆盖率,一共分 6 步:
- 编写生成覆盖率文件 coverage.ec 的类和方法
- build.gradle(app) 新增 jacoco 插件
- 打开覆盖率统计开关
- 覆盖率生成条件监听
- 安装 debug 版本
- 服务器生成 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
}
如图所示:
转载文章时务必注明原作者及原始链接,并注明「发表于 TesterHome 」,并不得对作品进行修改。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!