参考: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代码覆盖率:单元测试覆盖率+功能测试覆盖率的更多相关文章

  1. jacoco统计server端功能测试覆盖率

    jacoco可以统计,功能测试时,server代码调用的覆盖情况.这里对服务器端的java代码进行统计.   操作步骤如下:   第一步:更改server的启动脚本,使用jacocoagent.jar ...

  2. Jacoco收集单元测试、集成测试和系统功能测试覆盖率

    Jacoco收集单元测试.集成测试和系统功能测试覆盖率 2020-02-27  目录 1 安装版本2 被测系统代码示例3 收集单元测试覆盖率4 收集集成和功能测试覆盖率 代码覆盖率可在单元测试.系统测 ...

  3. jenkins+jacoco+ant+apache集成统计web端功能测试覆盖率

    一.覆盖率定义 作为一个测试人员,保证产品的软件质量是其工作首要目标,为了这个目标,测试人员常常会通过很多手段或工具来加以保证,覆盖率就是其中一环比较重要的环节. 我们通常会将测试覆盖率分为两个部分, ...

  4. Jenkins集成jacoco收集单元测试覆盖率

    Jenkins集成jacoco收集单元测试覆盖率 2020-02-28 目录 0 整体思路1 Jenkins创建JacocoIntegrateTestDemo项目2 配置源码管理3 配置Build4 ...

  5. jacoco + eclipse单元测试覆盖率

    概念 Jacoco:JaCoCo是一个开源的覆盖率工具,它针对的开发语言是java,其使用方法很灵活,可以嵌入到Ant.Maven中:可以作为Eclipse插件,可以使用其JavaAgent技术监控J ...

  6. 在jenkins和sonar中集成jacoco(一)--使用jacoco收集单元测试的覆盖率

    之前系统的持续集成覆盖率工具使用的是cobetura,使用的过程中虽然没什么问题,但感觉配置比较麻烦,现在准备改用jacoco这个覆盖率工具来代替它.接下来我介绍一下jenkins配置jacoco,并 ...

  7. 新开发项目Jacoco代码覆盖率

    一般只有新的项目才会去用JaCoCo工具看一下代码覆盖率, 一来看看测试有没有漏的测试用例 二来看看开发有没有留下冗余的代码 新开发项目Jacoco代码覆盖率后端接口打成jar包,进行启动 #exec ...

  8. 在Android中进行单元测试遇到的问题

    问题1.Cannot connect to VM  socket closed 在使用JUnit进行测试的时候,遇到这个问题.网上的解释是:使用Eclipse对Java代码进行调试,无论是远程JVM还 ...

  9. Android上的单元测试

    Android上的单元测试 http://www.sina.com.cn  2009年12月04日 16:07  IT168.com [IT168 技术文档]任何程序的开发都离不开单元测试来保证其健壮 ...

随机推荐

  1. linux中信号的API详解实例

    /************************************************************************* > File Name: signal.c ...

  2. Ubuntu18.04 root 登录

    默认安装Ubuntu18.04都是不允许以root用户进行登录的,想要以root用户进行登录需要进行一些操作,主要是以下几个步骤:第一步:以普通用户登录系统,创建root用户的密码在终端输入命令:su ...

  3. JavaFX窗体设置无边框

    public void start(Stage stage) throws Exception { longStart(); Parent root = FXMLLoader.load(getClas ...

  4. linux的fcntl函数

    fcntl可实现对指定文件描述符的各种操作,例如获取/设置 文件的是否可以被读写的状态,等其他状态. int fcntl (int __fd, int __cmd, ...);The remainin ...

  5. idea导入eclipse包乱码问题全局解决方案:

    IJ导入eclipse包乱码问题: 全局解决方案: 设置所在文件夹位置 1.从eclipse导入方式,发现还是乱码 2.导航栏点击文件->设置->editor->文件编码 将所在文件 ...

  6. [PDF] - 获取 RadioButtonList 控件值的方法

    背景 目标是通过 iTextSharp 读取 PDF 模板,填充内容后以生成新 PDF 文件.利用 福昕PDF编辑器个人版 可以获取到 RadioButtonList 的组名,但是获取不到每一个 Ra ...

  7. Python 模块初始化的时候,发生了什么?

    假设有一个 hello.py 的模块,当我们从别的模块调用 hello.py 的时候,会发生什么呢? 方便起见,我们之间在 hello.py 的目录下使用 ipython 导入了. hello.py ...

  8. SecureCRT系列:生成公私钥

    SecureCRT下载地址:http://www.portablesoft.org/securecrt-securefx-legacy-versions/1.打开我们的SecureCRT客户端,点击t ...

  9. Spring+SpringMVC+Mybatis(SSM)框架集成搭建

    Spring+SpringMVC+Mybatis框架集成搭建教程 一.背景 最近有很多同学由于没有过SSM(Spring+SpringMvc+Mybatis , 以下简称SSM)框架的搭建的经历,所以 ...

  10. BJFU-208-基于顺序存储结构的图书信息表的最贵图书的查找

    #include<stdio.h> #include<stdlib.h> #define MAX 1000 typedef struct{ double no; char na ...