jacoco的官网:EclEmma - JaCoCo Java Code Coverage Library
gradle语法:任务依赖 - Gradle 用户指南官方文档中文版 - UDN开源文档
在使用jacoco的时候,有时候需要将当前待测试的java类,添加到@PrepareForTest() 中,这会导致生成的行覆盖html中,当前类的覆盖率是为0,网上说比较好的解决办法是,使用jacoco offline模式,但是网上给的参考大部分都是maven文件,或者给的gradle文件使用不了。
github上提供了两个比较好的示例:
示例一:GitHub - MohammadAlavi1986/gradle-jacoo-offline-instrumentation
该示例是基于java程序的gradle配置,不使用android程序
示例二:https://github.com/h8MyJob/PowerMockJacocoDemo
该示例比较老了,运行起来有很多问题。
下面是我运行成功的配置(摸索了好久。。。)
其中,gradle版本是:gradle-7.5-bin.zip
顶层的build.gradle文件:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.4.1' apply false
id 'com.android.library' version '7.4.1' apply false
}
module的build.gradle文件:
//单元测试相关依赖
testImplementation("org.powermock:powermock-module-junit4:2.0.7")
testImplementation("org.powermock:powermock-module-junit4-rule:1.7.4")
testImplementation("org.powermock:powermock-api-mockito2:2.0.7")
testImplementation("org.powermock:powermock-core:2.0.7")
testImplementation("org.mockito:mockito-core:3.11.2")
testImplementation("junit:junit:4.13.2")
testImplementation("android.arch.core:core-testing:1.1.1")
testImplementation("org.mockito:mockito-inline:3.11.2")
最后 jacoco_offline.gradle 文件如下:
apply plugin: 'jacoco'
configurations {
jacocoAnt
jacocoRuntime
}
// 设置 jacoco offline 模式的Report目录
def jacocoOfflineDir = layout.buildDirectory.dir('jacocoOfflineReport')
jacoco {
toolVersion = "0.8.11" // 升级最新的版本
reportsDirectory = jacocoOfflineDir
}
def offline_instrumented_outputDir = "$buildDir.path/intermediates/classes-instrumented/debugCoverage"
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
// 设置原java文件的目录
def coverageSourceDirs = [
'src/main/java'
]
// 设置生成覆盖率报告时,需要排除的文件
def excludeFiles = [
'**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
'**/BuildConfig.*',
'**/**Bean.class',
'**/inputmethod/*',
'**/config/*.*',
'**/flex/*.*',
'**/AssetsUtils.class'
]
// 设置生成覆盖率报告时,仅仅支持的文件
def includeFiles = [
'**/BuildConfig.*',
'**/HCInfoUtil.*'
]
def classFileDir = fileTree(
dir: 'build/intermediates/javac/debug/classes',
excludes: excludeFiles
// includes: includeFiles
)
// (1)生成jacoco动态覆盖率报告(jacoco属于动态检查)
task jacocoTestReport(type: JacocoReport, dependsOn: "test") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories.from = classFileDir
getSourceDirectories().setFrom(files(coverageSourceDirs))
getExecutionData().setFrom(files('./build/jacoco/testDebugUnitTest.exec'))
}
jacocoTestReport {
reports {
xml.enabled false
html.enabled true
html.destination file("build/test-results/jacocoHtml")
}
}
/**
* jacoco offline instrumentation jacoco离线检查
* 先生成原始的预检测的*.class,然后在此基础上生成.exec文件,而不是在运行中生成检测的class文件
*/
task createOfflineTestCoverageReport(dependsOn: ['clean', 'instrument', 'testDebugUnitTest']) {
group = "Reporting"
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/javac/debug/classes")
}
sourcefiles {
fileset(dir: 'src/main/java')
}
}
// offline模式生成的报告路径,此报告不能排除文件,一般不用,而是在之后的task中生成 html 报告
// html(destdir: "$buildDir.path/reports/jacocoHtml")
}
}
}
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/javac/debug/classes")
}
}
}
// 将 offline 模式生成的exec文件拷贝到另一个目录,与jacoco动态生成区分
task copyTask(dependsOn:['createOfflineTestCoverageReport']) {
doLast{
copy {
from './build/jacoco/testDebugUnitTest.exec'
into './build/jacoco/offline'
}
}
}
// 删除 offline 生成的html报告,防止执行当前生成报告未更新
task deleteReportTask(type: Delete) {
doLast {
delete jacocoOfflineDir.get().toString()+'/jacocoTestReportOffline'
}
}
// 使用 offline 模式下的 exec 文件,生成html报告
task jacocoTestReportOffline(type: JacocoReport, dependsOn:['copyTask', 'deleteReportTask']) {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories.from = classFileDir
getSourceDirectories().setFrom(files(coverageSourceDirs))
getExecutionData().setFrom(files('./build/jacoco/offline/testDebugUnitTest.exec'))
}
// (2)生成 jacoco offline 模式的覆盖率报告
task GenerateJacocoOfflineCoverageReports(dependsOn:'jacocoTestReportOffline') {
group = "Reporting"
doLast {
println '生成 jacoco offline 报告的路径:'
println jacocoOfflineDir.get().toString() + '/jacocoTestReportOffline'
}
}