android 代码覆盖率,使用gradle+jacoco收集android手工代码覆盖率

1.背景介绍

在产品安卓端的测试过程中,新功能测试以及回归测试在手工测试的情况下,即便测试用例再详尽,因为没有覆盖率等客观数据的支持,难免可能会有疏漏之处。如果可以统计出手工代码覆盖率的情况,可以及时地补充测试用例。统计代码覆盖率的工具主要有Emma和Jacoco。

jacoco是Java Code Coverage的缩写,顾名思义,是Java代码覆盖率统计的主流工具之一。关于jacoco的原理可以移步jacoco原理介绍。在安卓项目的代码覆盖率统计使用了jacoco的离线插桩方式,在测试前先对文件进行插桩,然后生成插过桩的class或jar包,测试(单元测试、UI测试或者手工测试等)插过桩的class和jar包后,会生成动态覆盖信息到文件,最后统一对覆盖信息进行处理,并生成报告。

作为QA,统计代码覆盖率的首要问题就是,是否需要修改开发的核心源代码。通过调研,现在实现安卓手工代码覆盖率的方法主要有两大类,一类是在activity退出时增加覆盖率的统计(要修改核心源代码),一类是通过instrumentation调起被测APP,在instrumentation activity退出时增加覆盖率的统计(不修改核心源代码)。当然还有其他诸如通过Broadcast Receiver或者Service等方法。本文采用第二类instrumentation方法实现,构建方式是gradle。

2.生成手工代码覆盖率文件

1)首先要有一个安卓工程

这里我们可以自己写一个简单的工程,MainActivity中直接写个Hello World文本就好。

2)在代码主路径下新建test文件夹,新建3个类文件

1a4a81f09526

我们在java/下新建test文件夹,然后在test路径下新建三个类文件,分别是抽象类FinishListener,Instrumentation的Activity和instrumentation类。

FinishListener:

package coverage.netease.com.test;

public interface FinishListener {

void onActivityFinished();

void dumpIntermediateCoverage(String filePath);

}

InstrumentedActivity:

package coverage.netease.com.test;

import android.util.Log;

import coverage.netease.com.androidcoverage.MainActivity;

import coverage.netease.com.test.FinishListener;

import coverage.netease.com.test.FinishListener;

public class InstrumentedActivity extends MainActivity {

public static String TAG = "IntrumentedActivity";

private coverage.netease.com.test.FinishListener mListener;

public void setFinishListener(FinishListener listener) {

mListener = listener;

}

@Override

public void onDestroy() {

Log.d(TAG + ".InstrumentedActivity", "onDestroy()");

super.finish();

if (mListener != null) {

mListener.onActivityFinished();

}

}

}

JacocoInstrumentation:

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;

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().toString() + "/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();

}

@Override

public void onStart() {

super.onStart();

Looper.prepare();

InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);

activity.setFinishListener(this);

}

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 (Exception e) {

Log.d(TAG, e.toString(), e);

} finally {

if (out != null) {

try {

out.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

@Override

public void onActivityFinished() {

if (LOGD) Log.d(TAG, "onActivityFinished()");

if (mCoverage) {

generateCoverageReport();

}

finish(Activity.RESULT_OK, mResults);

}

}

3)修改Build.gradle文件

增加jacoco插件和打开覆盖率统计开关

apply plugin: 'com.android.application'

apply plugin: 'jacoco'

android {

compileSdkVersion 22

buildToolsVersion "22.0.1"

defaultConfig {

applicationId "coverage.netease.com.androidcoverage"

minSdkVersion 16

targetSdkVersion 22

versionCode 1

versionName "1.0"

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

}

debug {

/**打开覆盖率统计开关/

testCoverageEnabled = true

}

}

}

dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

}

4)在manifest.xml增加声明

在中声明InstrumentedActivity:

在manifest中声明使用SD卡权限:

最后,在manifest中单独声明JacocoInstrumentation:

android:handleProfiling="true"

android:label="CoverageInstrumentation"

/*这里android:name写明此instrumentation的全称*/

android:name="coverage.netease.com.test.JacocoInstrumentation"

/*这里android:targetPackage写明被测应用的包名*/

android:targetPackage="coverage.netease.com.androidcoverage"/>

5)在真机或者模拟器上运行一下app,并点击返回退出。

6)在命令行下通过adb shell am instrument命令调起app,具体命令是:

adb shell am instrument coverage.netease.com.androidcoverage/coverage.netease.com.test.JacocoInstrumentation

也就是adb shell am instrument /

7)调起app后我们就可以进行手工测试了,测试完成后点击返回键退出app。

此时,jacoco便将覆盖率统计信息写入/data/data//files/coverage.ec文件。

接下来我们需要新增gradle task,分析覆盖率文件生成覆盖率html报告。

3.生成覆盖率html报告

1)首先将coverage.ec文件拉到本地,置于指定目录下。

我们在FileExplorer中找到coverage.ec文件。

打开DDMS工具:

1a4a81f09526

1a4a81f09526

找到data/data//files/coverage.ec文件,点击左上角的Pull File From Device按钮,将该文件拖至入app根目录/build/outputs/code-coverage/connected下(文件夹没有的话可新建)。

2)新增gradle task,修改build.gradle文件为:

apply plugin: 'com.android.application'

apply plugin: 'jacoco'

android {

compileSdkVersion 22

buildToolsVersion "22.0.1"

defaultConfig {

applicationId "coverage.netease.com.androidcoverage"

minSdkVersion 16

targetSdkVersion 22

versionCode 1

versionName "1.0"

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

}

debug {

testCoverageEnabled = true

}

}

}

dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

}

def coverageSourceDirs = [

'../app/src/main/java'

]

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/debug',

excludes: ['**/R*.class',

'**/*$InjectAdapter.class',

'**/*$ModuleAdapter.class',

'**/*$ViewInjector*.class'

])

sourceDirectories = files(coverageSourceDirs)

executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")

doFirst {

new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->

if (file.name.contains('$$')) {

file.renameTo(file.path.replace('$$', '$'))

}

}

}

}

3)在命令行执行gradle jacocoTestReport

1a4a81f09526

4)查看覆盖率html报告

执行完gradle jacocoTestReport后,我们可以在app\build\reports\jacoco\jacocoTestReport\html目录下看到html报告。

1a4a81f09526

将index.html拖到浏览器中,可以看到具体的覆盖率数据啦。

1a4a81f09526

点击进去还可以看到源代码覆盖的情况哦。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值