Powermock Jacoco Gradle Android 项目的 0% 覆盖率
Powermock Jacoco Gradle 0% Coverage For Android Project
我们有一个 Android 项目,我们将 Powermock 用于一些测试用例,将 Jacoco 用于覆盖率报告。我们注意到我们的一些 类 返回为 0% 覆盖率,尽管它们确实被覆盖了。我们还观察到以下受影响 类.
的消息
"Classes ... do no match with execution data."
一些在线搜索显示 Powermock and Jacoco don't play well 和 离线检测 是一种可能的解决方法。
有没有人以前为 android 个项目使用过 gradle 离线检测脚本?
事后看来,我想这可以通过足够的 android 经验和在线阅读来解决。然而,当这篇文章落在我的腿上时,我对 Android、gradle 和 groovy 还是(现在仍然)比较陌生,所以我正在为下一个我写这篇文章 :-D
简而言之发生了什么(摘自 jacoco forum)
- 源文件被编译成非检测的class文件
- 未检测 class 文件已检测(离线预检测,或由 Java 代理在 运行 时间自动检测)
- 执行已检测的 classes 收集到 exec 文件
- 报告使用从 exec 文件和原始非检测 class 文件分析中获得的信息修饰源文件
- 生成报告时的消息“
Classes ... do no match with execution data.
”表示用于生成报告的 class 文件与 class 不同es 在检测之前。
解决方案
Jacoco Offline Instrumentation 页面提供了此摘录中脱机检测应该发生的主要步骤:
For such scenarios class files can be pre-instrumented with JaCoCo,
for example with the instrument Ant task. 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.
下面的脚本就是这样做的:
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")
}
}
}
用法
脚本可以复制到一个单独的文件中。例如:jacoco.gradle
在您的 build.gradle 中引用 jacoco 文件。例如:apply from: jacoco.gradle
确保正确的依赖关系:jacocoAnt 'org.jacoco:org.jacoco.ant:0.8.1:nodeps'
在命令行中运行:gradle clean & gradle createOfflineTestCoverageReport & gradle jacocoTestReport
gradle clean
将清除任何先前的 gradle 执行工件
gradle createOfflineTestCoverageReport
将创建离线工具,更改 class 路径的顺序,生成 .exec 文件
gradle jacocoTestReport
将 运行 测试并根据先前生成的 .exec 文件生成 jacoco 报告
感到迷茫?
我已经将 github Jacoco Powermock Android 项目与示例脚本放在一起,以重现和解决问题。它还包含有关解决方案的更多信息。
参考
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://groups.google.com/forum/#!msg/jacoco/5IqM4AibmT8/-x5w4kU9BAAJ
我们有一个 Android 项目,我们将 Powermock 用于一些测试用例,将 Jacoco 用于覆盖率报告。我们注意到我们的一些 类 返回为 0% 覆盖率,尽管它们确实被覆盖了。我们还观察到以下受影响 类.
的消息"Classes ... do no match with execution data."
一些在线搜索显示 Powermock and Jacoco don't play well 和 离线检测 是一种可能的解决方法。
有没有人以前为 android 个项目使用过 gradle 离线检测脚本?
事后看来,我想这可以通过足够的 android 经验和在线阅读来解决。然而,当这篇文章落在我的腿上时,我对 Android、gradle 和 groovy 还是(现在仍然)比较陌生,所以我正在为下一个我写这篇文章 :-D
简而言之发生了什么(摘自 jacoco forum)
- 源文件被编译成非检测的class文件
- 未检测 class 文件已检测(离线预检测,或由 Java 代理在 运行 时间自动检测)
- 执行已检测的 classes 收集到 exec 文件
- 报告使用从 exec 文件和原始非检测 class 文件分析中获得的信息修饰源文件
- 生成报告时的消息“
Classes ... do no match with execution data.
”表示用于生成报告的 class 文件与 class 不同es 在检测之前。
解决方案
Jacoco Offline Instrumentation 页面提供了此摘录中脱机检测应该发生的主要步骤:
For such scenarios class files can be pre-instrumented with JaCoCo, for example with the instrument Ant task. 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.
下面的脚本就是这样做的:
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")
}
}
}
用法
脚本可以复制到一个单独的文件中。例如:
jacoco.gradle
在您的 build.gradle 中引用 jacoco 文件。例如:
apply from: jacoco.gradle
确保正确的依赖关系:
jacocoAnt 'org.jacoco:org.jacoco.ant:0.8.1:nodeps'
在命令行中运行:
gradle clean & gradle createOfflineTestCoverageReport & gradle jacocoTestReport
gradle clean
将清除任何先前的 gradle 执行工件gradle createOfflineTestCoverageReport
将创建离线工具,更改 class 路径的顺序,生成 .exec 文件gradle jacocoTestReport
将 运行 测试并根据先前生成的 .exec 文件生成 jacoco 报告
感到迷茫?
我已经将 github Jacoco Powermock Android 项目与示例脚本放在一起,以重现和解决问题。它还包含有关解决方案的更多信息。
参考
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://groups.google.com/forum/#!msg/jacoco/5IqM4AibmT8/-x5w4kU9BAAJ