参考: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下查看解析的报告
查看报告: