经过多次测试,参考国内外诸多资料。现整理配置如下。
环境:
classpath “com.android.tools.build:gradle:7.0.2”
ext.kotlin_version = ‘1.6.0’
//jacoco.gradle
import org.gradle.api.internal.project.ProjectInternal
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.7"
}
android {
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
project.apply {
project.extensions.create("jacocoAndroidUnitTestReport",
JacocoAndroidUnitTestReportExtension,
JacocoAndroidUnitTestReportExtension.defaultExcludesFactory())
project.plugins.apply(JacocoPlugin)
Plugin plugin = findAndroidPluginOrThrow(project.plugins)
def variants = getVariants(project, plugin)
Task jacocoTestReportTask = findOrCreateJacocoTestReportTask(project.tasks)
variants.all { variant ->
def sourceDirs = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
def classesDir
if (variant.hasProperty('javaCompileProvider')) {
classesDir = variant.javaCompileProvider.get().destinationDir
} else {
classesDir = variant.javaCompile.destinationDir
}
def unitTestTask = tasks.getByName("test${variant.name.capitalize()}UnitTest")
def checkTask = tasks.getByName("connectedCheck")
def androidTestPath = "../app/build/outputs/code_coverage/debugAndroidTest/connected/"
def executionData = unitTestTask.jacoco.destinationFile.path
FileTree javaTree = project.fileTree(dir: classesDir, excludes: project.jacocoAndroidUnitTestReport.excludes)
def kotlinClassesDir = "${project.buildDir}/tmp/kotlin-classes/${variant.name}"
def kotlinTree =
project.fileTree(dir: kotlinClassesDir, excludes: project.jacocoAndroidUnitTestReport.excludes)
JacocoReport reportTask = project.tasks.create("jacoco${unitTestTask.name.capitalize()}Report",
JacocoReport)
reportTask.dependsOn unitTestTask
reportTask.dependsOn checkTask
reportTask.group = "Reporting"
reportTask.executionData.setFrom(project.fileTree(executionData) + project.fileTree(androidTestPath))
reportTask.sourceDirectories.setFrom(project.files(sourceDirs))
reportTask.classDirectories.setFrom(javaTree + kotlinTree)
reportTask.reports {
def destination = project.jacocoAndroidUnitTestReport.destination
xml.enabled = true
html.enabled true
html.destination new File((destination == null) ? "${project.buildDir}/jacoco/jacocoHtml" : "${destination.trim()}/jacocoHtml")
}
jacocoTestReportTask.dependsOn reportTask
}
}
class JacocoAndroidUnitTestReportExtension {
public static final Collection<String> androidDataBindingExcludes =
['android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/databinding/*Binding.class',
'**/databinding/*BindingImpl.class',
'**/databinding/*Sw600dpImpl.class',
'**/BR.*'].asImmutable()
public static final Collection<String> androidExcludes =
['**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*'].asImmutable()
public static final Collection<String> butterKnifeExcludes =
['**/*$ViewInjector*.*',
'**/*$ViewBinder*.*'].asImmutable()
public static final Collection<String> dagger2Excludes =
['**/*_MembersInjector.class',
'**/Dagger*Component.class',
'**/Dagger*Component$Builder.class',
'**/*Module_*Factory.class'].asImmutable()
public static final Collection<String> defaultExcludes =
(androidDataBindingExcludes + androidExcludes + butterKnifeExcludes + dagger2Excludes)
.asImmutable()
def static defaultExcludesFactory = { defaultExcludes }
Collection<String> excludes
boolean csv
boolean html
boolean xml
String destination
JacocoAndroidUnitTestReportExtension(Collection<String> excludes) {
this.excludes = excludes
this.csv = true
this.html = true
this.xml = true
this.destination = null
}
}
private static def getVariants(ProjectInternal project, Plugin plugin) {
boolean isLibraryPlugin = plugin.class.name.endsWith('.LibraryPlugin')
project.android[isLibraryPlugin ? "libraryVariants" : "applicationVariants"]
}
private static Plugin findAndroidPluginOrThrow(PluginContainer plugins) {
Plugin plugin = plugins.findPlugin('android') ?: plugins.findPlugin('android-library')
if (!plugin) {
throw new GradleException(
'You must apply the Android plugin or the Android library plugin before using the jacoco-android plugin')
}
plugin
}
private static Task findOrCreateJacocoTestReportTask(TaskContainer tasks) {
Task jacocoTestReportTask = tasks.findByName("jacocoTestReport")
if (!jacocoTestReportTask) {
jacocoTestReportTask = tasks.create("jacocoTestReport")
jacocoTestReportTask.group = "Reporting"
}
jacocoTestReportTask
}
使用方式:
在需要的项目的build.gradle 中配置apply from: "jacoco.gradle"即可。
打开右侧gradle侧边栏,点击reporting中的jacocoTestReport任务。
等待完成在build/jacoco/打开index.html即可
有效参考:
插件式配置,缺点是缺少对AndroidTest目录的配置。
踩坑日记之Gradle自定义JacocoReport跟Test task,缺点是只对java项目有效。
注意:
JaCoCo与PowerMock是不兼容的,覆盖率为0。
https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCo