Android-jacoco代码覆盖率:单元测试覆盖率+功能测试覆盖率
参考:https://docs.gradle.org/current/dsl/org.gradle.testing.jacoco.tasks.JacocoCoverageVerification.html
gradle库下载:https://maven.aliyun.com/mvn/view
案例参考来源:https://www.jianshu.com/p/1a4a81f09526
https://www.jianshu.com/p/1a4a81f09526
其他:https://testerhome.com/topics/8329
这几天折腾了很久,主要是现在的案例都是基于gradle3.1.3版本,我不想用旧版本的,查了一些资料,自己改了下代码,可以用了。
前情:
之前听说Android可以用jacoco+monkey做代码覆盖率测试,以前只做过一个spring的jacoco的单元测试覆盖率的demo,没想过Android可以将功能和jacoco联合在一起,这几天很闲就搞了一下。
准备工作:
要有Android项目源码,不用修改项目主体的核心代码,但是需要写一些jacoco的代码,主要是利用instrument在acitivity结束时记录代码覆盖率;
具体内容分两块:
一,Android项目的单元测试代码覆盖率:
利用AndroidStudio自带的task来查看当前AndroidTest文件夹下的单元测试用例覆盖率情况
编辑build.gradle
android {
...
defaultConfig {
...
testInstrumentationRunnerArguments clearPackageData: 'true'
// 执行instrumentation测试时清除缓存
}
buildTypes {
debug {
testCoverageEnabled = true
/**打开覆盖率统计开关
*/
}
}
安装debug包

执行AndroidTest的覆盖率测试并输出报告

执行日志是这样的:
这里摘取的是执行AndroidTest单元测试的片段,通过adb发送instrument命令到手机,执行测试,获取覆盖率数据,并从手机中down下来:
Executing tasks: [createDebugAndroidTestCoverageReport]
...
> Task :app:connectedDebugAndroidTest
...
05:40:39 V/ddms: execute: running am instrument -w -r -e coverageFile /data/data/com.patech.testApp/coverage.ec -e coverage true -e clearPackageData true com.patech.testApp.test/androidx.test.runner.AndroidJUnitRunner
...
05:40:41 V/InstrumentationResultParser: com.patech.testApp.EspressoTest:
...
05:40:58 V/InstrumentationResultParser: Time: 17.669
05:40:58 V/InstrumentationResultParser:
05:40:58 V/InstrumentationResultParser: OK (5 tests)
05:40:58 V/InstrumentationResultParser:
05:40:58 V/InstrumentationResultParser:
05:40:58 V/InstrumentationResultParser: Generated code coverage data to /data/data/com.patech.testApp/coverage.ec
05:40:58 V/InstrumentationResultParser: INSTRUMENTATION_CODE: -1
...
05:40:59 I/XmlResultReporter: XML test result file generated at D:\androidStudio\MyApplication\app\build\outputs\androidTest-results\connected\TEST-VOG-AL10 - 9-app-.xml. Total tests 5, passed 5,
05:40:59 V/ddms: execute 'am instrument -w -r -e coverageFile /data/data/com.patech.testApp/coverage.ec -e coverage true -e clearPackageData true com.patech.testApp.test/androidx.test.runner.AndroidJUnitRunner' on 'APH0219430006864' : EOF hit. Read: -1
...
05:40:59 D/com.patech.testApp.coverage.ec: Downloading com.patech.testApp.coverage.ec from device 'APH0219430006864'
...
执行完毕后查看build下的reports的详情


二.编写jacoco+instrument的代码,执行功能测试后,在本地生成ec文件,传到pc端后解析成html格式,查看功能测试操作的代码覆盖率执行情况
1.编写FinishListener接口
public interface FinishListener {
void onActivityFinished();
void dumpIntermediateCoverage(String filePath);
}
编写jacocoInstrumentation方法,实现上面这个接口,网上抄来的,实现了执行完成后生成覆盖率文件并保存到手机本地:
package com.patech.test; import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log; import com.patech.testApp.InstrumentedActivity; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException; public class JacocoInstrumentation extends Instrumentation implements FinishListener{ public static String TAG = "JacocoInstrumentation:";
private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec"; private final Bundle mResults = new Bundle(); private Intent mIntent;
//LOGD 调试用布尔
private static final boolean LOGD = true; private boolean mCoverage = true; private String mCoverageFilePath; public JacocoInstrumentation(){ } @Override
public void onCreate(Bundle arguments) {
Log.d(TAG, "onCreate(" + arguments + ")");
super.onCreate(arguments);
//DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath() + "/coverage.ec"; File file = new File(DEFAULT_COVERAGE_FILE_PATH);
if (!file.exists()) {
try {
file.createNewFile();
}catch (IOException e) {
Log.d(TAG, "异常 :" + e);
e.printStackTrace();
}
} if (arguments != null) {
mCoverageFilePath = arguments.getString("coverageFile");
} mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
start();
} public void onStart() {
if (LOGD)
Log.d(TAG,"onStart()");
super.onStart(); Looper.prepare();
/* InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);
activity.setFinishListener(this);*/
} private boolean getBooleanArgument(Bundle arguments, String tag) {
String tagString = arguments.getString(tag);
return tagString != null && Boolean.parseBoolean(tagString);
} private String getCoverageFilePath() {
if (mCoverageFilePath == null) {
return DEFAULT_COVERAGE_FILE_PATH;
}else {
return mCoverageFilePath;
}
} private void generateCoverageReport() {
Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());
OutputStream out = null;
try {
out = new FileOutputStream(getCoverageFilePath(),false);
Object agent = Class.forName("org.jacoco.agent.rt.RT")
.getMethod("getAgent")
.invoke(null); out.write((byte[]) agent.getClass().getMethod("getExecutionData",boolean.class)
.invoke(agent,false));
} catch (FileNotFoundException e) {
Log.d(TAG, e.toString(), e);
} catch (IOException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} public void UsegenerateCoverageReport() {
generateCoverageReport();
} private boolean setCoverageFilePath(String filePath){
if (filePath != null && filePath.length() > 0) {
mCoverageFilePath = filePath;
}
return false;
} private void reportEmmaError(Exception e) {
reportEmmaError(e);
} private void reportEmmaError(String hint, Exception e) {
String msg = "Failed to generate emma coverage. " +hint;
Log.e(TAG, msg, e);
mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER,"\nError: " + msg);
} @Override
public void onActivityFinished() {
if (LOGD) {
Log.d(TAG,"onActivityFinished()");
}
finish(Activity.RESULT_OK,mResults);
} @Override
public void dumpIntermediateCoverage(String filePath) {
if (LOGD) {
Log.d(TAG,"Intermidate Dump Called with file name :" + filePath);
}
if (mCoverage){
if (!setCoverageFilePath(filePath)) {
if (LOGD) {
Log.d(TAG,"Unable to set the given file path :" +filePath + "as dump target.");
}
}
generateCoverageReport();
setCoverageFilePath(DEFAULT_COVERAGE_FILE_PATH);
}
}
}
2.修改AndroidManifest.xml文件,添加往手机读写的权限,以及instrument的设置,该标签与application标签同级:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Jacoco权限 -->
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <instrumentation
android:name="com.patech.test.JacocoInstrumentation"
android:handleProfiling="true"
android:label="CoverageInstrumentation"
android:targetPackage="com.patech.testApp" />
3.编写jacoco.gradle,用于解析ec,转换成html或者其他格式的报告:
apply plugin: 'jacoco'
//https://docs.gradle.org/current/userguide/jacoco_plugin.html
jacoco {
toolVersion = "0.8.4"
}
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug",
// includes: ["**/*Presenter.*"],
excludes: ['**/R*.class',
'**/*$InjectAdapter.class',
'**/*$ModuleAdapter.class',
'**/*$ViewInjector*.class'
])//指定类文件夹、包含类的规则及排除类的规则,这里我们生成所有Presenter类的测试报告
def coverageSourceDirs = "${project.projectDir}/src/main/java" //指定源码目录
def reportDirs="$buildDir/outputs/reports/jacoco/jacocoTestReport"
reports {
xml.enabled = true
html.enabled = true
}
// destinationFile=file(reportDirs)
classDirectories = files(debugTree)
sourceDirectories = files(coverageSourceDirs)
executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
}
4.连接手机,安装apk后执行adb语句,通过jacoco开启应用:
adb shell am instrument -w -r com.patech.testApp/com.patech.testcoverage.test.JacocoInstrumentation
5.可以在手机上开始做功能测试了,测试完毕后导出ec文件:
adb pull mnt/sdcard/coverage.ec C:\Users\user\Desktop\testReport\jacoco
6.将ec文件放入build/outputs/code-coverage/connected下

执行jacocoTestReport的task
在build/reports/jacoco/jacocoTestReport下查看解析的报告

查看报告:

Android-jacoco代码覆盖率:单元测试覆盖率+功能测试覆盖率的更多相关文章
- jacoco统计server端功能测试覆盖率
jacoco可以统计,功能测试时,server代码调用的覆盖情况.这里对服务器端的java代码进行统计. 操作步骤如下: 第一步:更改server的启动脚本,使用jacocoagent.jar ...
- Jacoco收集单元测试、集成测试和系统功能测试覆盖率
Jacoco收集单元测试.集成测试和系统功能测试覆盖率 2020-02-27 目录 1 安装版本2 被测系统代码示例3 收集单元测试覆盖率4 收集集成和功能测试覆盖率 代码覆盖率可在单元测试.系统测 ...
- jenkins+jacoco+ant+apache集成统计web端功能测试覆盖率
一.覆盖率定义 作为一个测试人员,保证产品的软件质量是其工作首要目标,为了这个目标,测试人员常常会通过很多手段或工具来加以保证,覆盖率就是其中一环比较重要的环节. 我们通常会将测试覆盖率分为两个部分, ...
- Jenkins集成jacoco收集单元测试覆盖率
Jenkins集成jacoco收集单元测试覆盖率 2020-02-28 目录 0 整体思路1 Jenkins创建JacocoIntegrateTestDemo项目2 配置源码管理3 配置Build4 ...
- jacoco + eclipse单元测试覆盖率
概念 Jacoco:JaCoCo是一个开源的覆盖率工具,它针对的开发语言是java,其使用方法很灵活,可以嵌入到Ant.Maven中:可以作为Eclipse插件,可以使用其JavaAgent技术监控J ...
- 在jenkins和sonar中集成jacoco(一)--使用jacoco收集单元测试的覆盖率
之前系统的持续集成覆盖率工具使用的是cobetura,使用的过程中虽然没什么问题,但感觉配置比较麻烦,现在准备改用jacoco这个覆盖率工具来代替它.接下来我介绍一下jenkins配置jacoco,并 ...
- 新开发项目Jacoco代码覆盖率
一般只有新的项目才会去用JaCoCo工具看一下代码覆盖率, 一来看看测试有没有漏的测试用例 二来看看开发有没有留下冗余的代码 新开发项目Jacoco代码覆盖率后端接口打成jar包,进行启动 #exec ...
- 在Android中进行单元测试遇到的问题
问题1.Cannot connect to VM socket closed 在使用JUnit进行测试的时候,遇到这个问题.网上的解释是:使用Eclipse对Java代码进行调试,无论是远程JVM还 ...
- Android上的单元测试
Android上的单元测试 http://www.sina.com.cn 2009年12月04日 16:07 IT168.com [IT168 技术文档]任何程序的开发都离不开单元测试来保证其健壮 ...
随机推荐
- Linux下安装redis以及常用命令
https://blog.csdn.net/zgf19930504/article/details/51850594 安装: 1.获取redis资源 wget http://download.redi ...
- Appium移动自动化测试-----(二)安装 Android SDK
安装Android SDK Android SDK(Software Development Kit,软件开发工具包)提供了 Android API 库和开发工具构建,测试和调试应用程序.简单来讲,A ...
- 洛谷 题解 P1550 【[USACO08OCT]打井Watering Hole】
本题看似很难,实际上思路非常简单--如果你想通了. 首先有一个问题:图中有几个点?大部分的人会回答\(n\)个点.错了,有\(n+1\)个. 多出来的那个点在哪?关键在于你要理解每一个决策的意义.实际 ...
- Mysql 千万数据快速导入
最近碰到个项目,需要 千万条数据入库的问题,有原本的 类 csv 文件导入, 统计了下 数据行大概有 1400W 行之多 二话不说, 建表,直接 load LOAD DATA LOCAL INFIL ...
- MySQL常用的系统函数
MySQL常用的系统函数 2019年01月17日 17:49:14 pan_junbiao 阅读数 155 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csd ...
- mysql创建唯一索引UNIQUE INDEX,以及报错“#失败原因: [Execute: Duplicate entry '733186700' for key 'uniq_video_id_index']”
要给t_video_prods表的video_id字段创建唯一所以,可以使用下面这条语句: alter table t_video_prods add UNIQUE INDEX `uniq_video ...
- shell 学习笔记2-shell-test
一.字符串测试表达式 前面一篇介绍:什么是shell,shell变量请参考: shell 学习笔记1-什么是shell,shell变量 1.字符串测试表达式参数 字符串需要用""引 ...
- hoj 棋盘问题 状压入个门
大概题意是:有一个n*m的棋盘,在这个棋盘里边放k个旗子,要求每一行每一列都不能存在一对旗子相邻,问最后总共的方案数. 我们先来考虑个简单的,假如说只有一行,要求在这一行里边填充k个旗子,要求任意两个 ...
- Android SDK版本号 与 API Level 对应关系 201911
API是开发用的,所以API LEVEL可以认为是内部的:而SDK的版本提供了新特性给用户,是外部可见的. 可以查看以下网址以获取最新的对应关系: http://developer.android. ...
- VC串口通讯,WriteFile或ReadFile没有任何返回??
别犯低级错误,一定要设置读写超时!!!