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

Category: 工具 Tagged: android jacoco


Page 1 of 1