Android Jacoco coverage 使用以及相关问题
Thu 01 December 2016
背景
在项目中,为了实现精准测试,一方面想能够查看手工测试是否有效,了解手工测试对代码的覆盖率,另一方面希望能够将手工执行的 case 与 测试代码联系起来,所以需要实现手工测试覆盖率的统计计算。通过调查了解,选用 jacoco 来实现。
项目中如何配置生成手工测试覆盖率
build.gradle(app)
需要在这个gradle文件中设置debug模式下开启记录覆盖率,并添加 gradle jacoco plugin依赖。(感觉即使不配置plugin也能够生成覆盖率,不知道是不是android plugin已经集成了相关的配置。但是后面生成覆盖率报告的时候需要用到jacoco plugin)
android{
...
buildTypes {
...
debug {
testCoverageEnabled true
}
...
}
...
}
...
apply plugin: 'jacoco'
jacoco{
toolVersion = "0.7.5+"
}
AndroidManifest.xml
还需要给应用添加读取外部存储的权限。
<manifest>
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>
代码中
代码中在需要触发生成覆盖率的地方(比如程序退出时,或者UncaughtExceptionHandler里面)添加以下代码:
java.io.OutputStream out = null;
try {
java.io.File coverageDir = new java.io.File("/mnt/sdcard/coverage/"); //这里是写入覆盖率工具的 sd 卡上的目录
if(!coverageDir.exists()){
coverageDir.mkdirs();
}
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmssSSS");
String coverageFileName = sdf.format(new java.util.Date())+".ec";
java.io.File file = new java.io.File(coverageDir,coverageFileName);
out = new java.io.FileOutputStream(file, 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, true)); //第二个bool 参数是指,获得数据后,是否reset data,重新生成覆盖数据
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
}
生成覆盖率文件后,如何生成报告
Merge多个覆盖率执行文件
如果手工测试多次,生成多个覆盖率文件,要想查看所有这些测试总共覆盖情况,首先需要 Merge 覆盖率执行结果文件。
build.gradle(app)
//首先先删除旧的merge结果文件
task removeOldMergeEc(type: Delete) {
delete "$buildDir/mergedcoverage.ec"
}
task mergeReport(type:JacocoMerge,dependsOn:removeOldMergeEc){
group = "Reporting"
description = "merge jacoco report."
destinationFile= file("$buildDir/mergedcoverage.ec")
//这里的ec_dir是存储ec文件的文件夹
FileTree tree = fileTree("$ec_dir") {
include '**/*.ec'
}
executionData = tree
}
运行命令:gradle mergeReport -Pec_dir="/the/ec/dir"
注意:这里的dir必须是绝对路径。
生成测试报告
build.gradle
task jacocoTestReport(type: JacocoReport,dependsOn:mergeReport) {
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/mergedcoverage.ec")
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
运行命令:gradle jacocoTestReport
遇到的问题
java.io.FileNotFoundException: /jacoco.exec: open failed: EROFS (Read-only file system)
这个问题,我尝试了一下,会出现在log里,但是不影响使用,也不影响生成覆盖率结果。
java.io.IOException: Incompatible version 1007
merge report的时候会出现下面的错误.
java.io.IOException: Incompatible version 1007.
at org.jacoco.core.data.ExecutionDataReader.readHeader(ExecutionDataReader.java:127)
at org.jacoco.core.data.ExecutionDataReader.readBlock(ExecutionDataReader.java:107)
at org.jacoco.core.data.ExecutionDataReader.read(ExecutionDataReader.java:87)
at org.jacoco.core.tools.ExecFileLoader.load(ExecFileLoader.java:59)
at org.jacoco.ant.MergeTask.load(MergeTask.java:85)
... 84 more
查了很多,都说是jacoco版本的问题,我的相关版本是:
- android studio: 2.2
- buildToolsVersion "23.0.3"
当使用 0.7.4+或者0.7.1.XXX的时候就会出错,改成下面的0.7.5+就没有问题了。所以出现这种错误的时候,多尝试改改这个版本。
jacoco{
toolVersion = "0.7.5+"
}
还有,当出现这个问题的时候,我曾一度认为是不是我的覆盖率结果文件不完整,结果不对,尝试了一下,如果是这种问题的话,会有以下的报错信息:
Caused by: java.io.IOException: Invalid execution data file.
at org.jacoco.core.data.ExecutionDataReader.read(ExecutionDataReader.java:84)
at org.jacoco.core.tools.ExecFileLoader.load(ExecFileLoader.java:59)
at org.jacoco.ant.MergeTask.load(MergeTask.java:85)
... 84 more
生成的覆盖率报告无法链接到代码
一般是因为设置的sourceDirectories不正确,需要设置到java代码的根目录,比如'src/main/java/'
Reference
jacoco agent github repository: https://github.com/jacoco/jacoco/tree/master/org.jacoco.agent.rt/src/org/jacoco/agent/rt