简体   繁体   English

Powermock Jacoco Gradle 0% 覆盖率 Android 项目

[英]Powermock Jacoco Gradle 0% Coverage For Android Project

We have an Android project, and we're using Powermock for some of our test cases and Jacoco for coverage report.我们有一个 Android 项目,我们在一些测试用例中使用 Powermock,在覆盖率报告中使用 Jacoco。 We noticed that some our classes are returning as 0% coverage although they are indeed covered.我们注意到我们的一些类返回为0%覆盖率,尽管它们确实被覆盖了。 We also observed the message below for affected classes.我们还针对受影响的课程观察到以下消息。

"Classes ... do no match with execution data."

A few searches online show that Powermock and Jacoco don't play well and that Offline Instrumentation is a possible workaround.一些在线搜索表明, Powermock 和 Jacoco 不能很好地发挥作用,而离线仪器是一种可能的解决方法。

Has anyone used gradle Offline Instrumentation script for android projects before?有没有人以前在android项目中使用过gradle离线检测脚本?

In hindsight, I guess this can be solved with enough android experience and online perusing.事后看来,我想这可以通过足够的安卓体验和在线阅读来解决。 However, I was (and still am) relatively new to Android, gradle, and groovy when this fell on my lap, so I'm writing this for the next me :-D然而,当它落在我的腿上时,我(现在仍然)对 Android、gradle 和 groovy 还比较陌生,所以我正在为下一个我写这篇文章:-D

WHAT IS HAPPENING IN A NUTSHELL ( excerpt from a jacoco forum )简而言之摘自 jacoco 论坛

  • Source file is compiled into non-instrumented class file源文件被编译成非检测类文件
  • Non-instrumented class file is instrumented (either pre-instrumented offline, or automatically at runtime by Java agent)非检测类文件被检测(离线预检测,或由 Java 代理在运行时自动检测)
  • Execution of instrumented classes collected into exec file执行收集到 exec 文件中的检测类
  • report decorates source files with information obtained from analysis of exec file and original non-instrumented class files报告使用从 exec 文件和原始非检测类文件的分析中获得的信息来修饰源文件
  • Message " Classes ... do no match with execution data. " during generation of report means that class files used for generation of report are not the same as classes prior to instrumentation.消息“ Classes ... do no match with execution data. ”在报告生成期间意味着用于生成报告的类文件与检测之前的类不同。

SOLUTION解决方案

The Jacoco Offline Instrumentation page provides the main steps that should occur for offline instrumentation in this excerpt: Jacoco Offline Instrumentation页面在此摘录中提供了离线检测应该发生的主要步骤:

For such scenarios class files can be pre-instrumented with JaCoCo, for example with the instrument Ant task.对于此类场景,可以使用 JaCoCo 预先检测类文件,例如使用仪器 Ant 任务。 At runtime the pre-instrumented classes needs be on the classpath instead of the original classes.在运行时,预先检测的类需要在类路径上,而不是原始类。 In addition jacocoagent.jar must be put on the classpath.此外 jacocoagent.jar 必须放在类路径上。

The script below does exactly that:下面的脚本正是这样做的:

    apply plugin: 'jacoco'

configurations {
    jacocoAnt
    jacocoRuntime
}

jacoco {
    toolVersion = "0.8.1"
}

def offline_instrumented_outputDir = "$buildDir.path/intermediates/classes-instrumented/debug"

tasks.withType(Test) {
    jacoco.includeNoLocationClasses = true
}

def coverageSourceDirs = [
        'src/main/java'
]

task jacocoTestReport(type: JacocoReport, dependsOn: "test") {
    group = "Reporting"

    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(
            dir: 'build/intermediates/classes/debug',
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/BuildConfig.*',
                       '**/MainActivity.*']
    )

    sourceDirectories = files(coverageSourceDirs)
    executionData = files('build/jacoco/testDebugUnitTest.exec')
}

jacocoTestReport {
    reports {
        xml.enabled  true
        html.enabled  true
        html.destination file("build/test-results/jacocoHtml")
    }
}

/* This task is used to create offline instrumentation of classes for on-the-fly instrumentation coverage tool like Jacoco. See jacoco classId
     * and Offline Instrumentation from the jacoco site for more info.
     *
     * In this case, some classes mocked using PowerMock were reported as 0% coverage on jacoco & Sonarqube. The issue between PowerMock and jacoco
     * is well documented, and a possible solution is offline Instrumentation (not so well documented for gradle).
     *
     * In a nutshell, this task:
     *  - Pre-instruments the original *.class files
     *  - Puts the instrumented classes path at the beginning of the task's classpath (for report purposes)
     *  - Runs test & generates a new exec file based on the pre-instrumented classes -- as opposed to on-the-fly instrumented class files generated by jacoco.
     *
     * It is currently not implemented to run prior to any other existing tasks (like test, jacocoTestReport, etc...), therefore, it should be called
     * explicitly if Offline Instrumentation report is needed.
     *
     *  Usage: gradle clean & gradle createOfflineInstrTestCoverageReport & gradle jacocoTestReport
     *   - gradle clean //To prevent influence from any previous task execution
     *   - gradle createOfflineInstrTestCoverageReport //To generate *.exec file from offline instrumented class
     *   - gradle jacocoTestReport //To generate html report from newly created *.exec task
     */
task createOfflineTestCoverageReport(dependsOn: ['instrument', 'testDebugUnitTest']) {
    doLast {
        ant.taskdef(name: 'report',
                classname: 'org.jacoco.ant.ReportTask',
                classpath: configurations.jacocoAnt.asPath)
        ant.report() {
            executiondata {
                ant.file(file: "$buildDir.path/jacoco/testDebugUnitTest.exec")
            }
            structure(name: 'Example') {
                classfiles {
                    fileset(dir: "$project.buildDir/intermediates/classes/debug")
                }
                sourcefiles {
                    fileset(dir: 'src/main/java')
                }
            }
            //Uncomment if we want the task to generate jacoco html reports. However, the current script does not exclude files.
            //An alternative is to used jacocoTestReport after this task finishes
            //html(destdir: "$buildDir.path/reports/jacocoHtml")
        }
    }
}

/*
 * Part of the Offline Instrumentation process is to add the jacoco runtime to the class path along with the path of the instrumented files.
 */
gradle.taskGraph.whenReady { graph ->
    if (graph.hasTask(instrument)) {
        tasks.withType(Test) {
            doFirst {
                systemProperty 'jacoco-agent.destfile', buildDir.path + '/jacoco/testDebugUnitTest.exec'
                classpath = files(offline_instrumented_outputDir) + classpath + configurations.jacocoRuntime
            }
        }
    }
}

/*
 *  Instruments the classes per se
 */
task instrument(dependsOn:'compileDebugUnitTestSources') {
    doLast {
        println 'Instrumenting classes'

        ant.taskdef(name: 'instrument',
                classname: 'org.jacoco.ant.InstrumentTask',
                classpath: configurations.jacocoAnt.asPath)

        ant.instrument(destdir: offline_instrumented_outputDir) {
            fileset(dir: "$buildDir.path/intermediates/classes/debug")
        }
    }
}

Usage用法

  • The script can be copied into a separate file.可以将脚本复制到单独的文件中。 For instance: jacoco.gradle例如: jacoco.gradle

  • Reference the jacoco file in your build.gradle.在 build.gradle 中引用 jacoco 文件。 For instance: apply from: jacoco.gradle例如: apply from: jacoco.gradle

  • Ensure proper dependencies: jacocoAnt 'org.jacoco:org.jacoco.ant:0.8.1:nodeps'确保正确的依赖关系: jacocoAnt 'org.jacoco:org.jacoco.ant:0.8.1:nodeps'

  • In command line run: gradle clean & gradle createOfflineTestCoverageReport & gradle jacocoTestReport在命令行中运行: gradle clean & gradle createOfflineTestCoverageReport & gradle jacocoTestReport

    gradle clean will wipe out any previous gradle execution artifacts gradle clean将清除任何以前的 gradle 执行工件

    gradle createOfflineTestCoverageReport will create offline instrumentation, change order of classpath, generate .exec file gradle createOfflineTestCoverageReport将创建离线检测,更改类路径的顺序,生成 .exec 文件

    gradle jacocoTestReport will run test and generate jacoco report based on previously generated .exec file gradle jacocoTestReport将运行测试并根据之前生成的 .exec 文件生成 jacoco 报告

Feeling Lost?失落的感觉?

I've put together a github Jacoco Powermock Android project with sample scripts to reproduce and fix the issue.我已经将 github Jacoco Powermock Android项目与示例脚本放在一起,以重现和解决问题。 It also contains more information about the solution.它还包含有关解决方案的更多信息。

REFERENCE参考

https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCo
https://www.jacoco.org/jacoco/trunk/doc/classids.html
https://www.jacoco.org/jacoco/trunk/doc/offline.html
https://github.com/powermock/powermock-examples-maven/tree/master/jacoco-offline
https://automated-testing.info/t/jacoco-offline-instrumentations-for-android-gradle/20121
https://stackoverflow.com/questions/41370815/jacoco-offline-instrumentation-gradle-script/42238982#42238982
https://groups.google.com/forum/#!msg/jacoco/5IqM4AibmT8/-x5w4kU9BAAJ

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM