gradle jacoco
·
多模块项目 Jacoco 配置指南
在多模块项目中配置 Jacoco 需要创建一个聚合报告来合并所有模块的覆盖率数据。以下是详细的配置说明(使用 Gradle Kotlin DSL):
根项目配置 (根目录下 build.gradle.kts)
plugins {
jacoco
}
// 配置所有子模块的 Jacoco
subprojects {
apply(plugin = "jacoco")
// 配置 Jacoco 版本
jacoco {
toolVersion = "0.8.12"
}
// 创建任务运行测试并生成覆盖率数据
tasks.withType<Test> {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
finalizedBy("jacocoTestReport") // 确保测试后生成报告
}
// 每个模块自己的报告任务
tasks.register<JacocoReport>("jacocoTestReport") {
group = "verification"
description = "Generates Jacoco coverage report for ${project.name}"
// 收集覆盖率数据
executionData.from(fileTree(project.buildDir) {
include("**/*.exec", "**/*.ec")
})
// 配置源文件和类文件
sourceDirectories.from(files("$projectDir/src/main/java"))
classDirectories.from(files(fileTree("$buildDir/classes") {
exclude(
"**/R.class",
"**/R$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test.*",
"**/generated/**"
)
}))
reports {
xml.required.set(true)
html.required.set(true)
csv.required.set(false)
}
}
}
// 创建聚合报告任务
tasks.register<JacocoReport>("jacocoAggregateReport") {
group = "verification"
description = "Generates aggregate Jacoco coverage report"
// 依赖所有子模块的报告任务
val projects = subprojects.filter {
// 排除不需要覆盖率的模块
it.path != ":app" && !it.name.contains("test")
}
dependsOn(projects.map { ":${it.name}:jacocoTestReport" })
// 收集所有子模块的执行数据
executionData.from(projects.map {
fileTree(it.buildDir) {
include("jacoco/*.exec", "outputs/code-coverage/*.ec")
}
})
// 设置源文件和类文件
sourceDirectories.from(
files(projects.map { "$it.projectDir/src/main/java" })
)
classDirectories.from(
files(projects.map {
fileTree("${it.buildDir}/classes") {
exclude(
"**/R.class",
"**/R$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test.*",
"**/generated/**"
)
}
})
)
reports {
xml.required.set(true)
html.required.set(true)
csv.required.set(false)
// 设置报告输出目录
html.outputLocation.set(layout.buildDirectory.dir("reports/jacoco/html"))
xml.outputLocation.set(layout.buildDirectory.dir("reports/jacoco/jacocoAggregateReport.xml"))
}
doFirst {
executionData.forEach { println("Including execution data: $it") }
}
}
模块级配置 (每个模块的 build.gradle.kts)
对于每个子模块,只需要基本的 Jacoco 配置(根项目已处理大部分):
// 在子模块的 build.gradle.kts 中
// 确保 Jacoco 配置
jacoco {
toolVersion = "0.8.12"
}
android {
// 确保测试覆盖率已启用
buildTypes {
getByName("debug") {
enableUnitTestCoverage = true
enableAndroidTestCoverage = true
}
}
}
tasks.withType<Test> {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}
dependencies {
// 为 Android 测试添加覆盖率支持
androidTestImplementation("androidx.test:runner:1.5.0")
androidTestImplementation("androidx.test:rules:1.5.0")
}
特殊情况的处理技巧
1. 排除特定模块
subprojects {
// 排除不需要覆盖率的模块
if (project.name == "no-coverage-module") {
tasks.matching { it.name.contains("jacoco") }.configureEach {
enabled = false
}
return@subprojects
}
// ...
}
2. 处理 Android Build Variants
tasks.register<JacocoReport>("jacocoTestReport") {
// 针对不同构建变体
val variant = "debug" // 或其他变体
// 使用特定变体的类目录
classDirectories.setFrom(fileTree("$buildDir/intermediates/javac/$variant/classes"))
// 收集对应变体的执行数据
executionData.setFrom(fileTree(buildDir) {
include("**/jacoco/test${variant.capitalize()}UnitTest.exec")
})
}
3. 合并单元测试和仪器化测试覆盖率
executionData.from(
fileTree(buildDir) {
include(
"jacoco/testDebugUnitTest.exec", // 单元测试
"outputs/code_coverage/*.ec" // 仪器化测试
)
}
)
4. 处理多版本兼容性
// 兼容旧 AGP 版本
val classesPath = if (project.hasProperty("android")) {
// Android 模块路径
"$buildDir/intermediates/javac/debug/classes"
} else {
// 纯 Java/Kotlin 模块路径
"$buildDir/classes/kotlin/main"
}
classDirectories.setFrom(files(fileTree(classesPath) {
// 排除规则
}))
运行聚合报告
# 生成所有模块的覆盖率数据
./gradlew testDebugUnitTest connectedDebugAndroidTest
# 生成聚合报告
./gradlew jacocoAggregateReport
# 打开 HTML 报告
open app/build/reports/jacoco/html/index.html
常见问题解决
1. 报告显示 "0% 覆盖率"
解决步骤:
- 检查是否使用了正确的构建变体(如 debug)
- 验证每个模块的
.exec文件是否存在:find . -name "*.exec" - 确保
executionData路径正确
2. "Execution data path contains no files"
解决方案: 确保所有子模块都正确生成了报告
# 手动执行所有模块的测试
./gradlew testDebugUnitTest --continue
3. 重复计入某些类
解决方法: 增强排除规则
classDirectories.from(files(projects.map {
fileTree("${it.buildDir}/classes") {
exclude(
// 添加具体排除模式
"**/di/**",
"**/binding/**",
"**/*_Factory.*"
)
}
}))
最佳实践建议
-
使用模块化排除
// 在根项目配置中 tasks.named("jacocoAggregateReport") { classDirectories.from(files( projects.filterNot { it.name == "data" }.map { ... } )) } -
集成持续集成系统
# GitHub Actions 示例 - name: Generate coverage report run: ./gradlew jacocoAggregateReport - name: Upload coverage uses: codecov/codecov-action@v3 with: files: build/reports/jacoco/jacocoAggregateReport.xml -
增量覆盖率统计
jacocoTestCoverageVerification { violationRules { rule { limit { minimum = 0.8 counter = 'LINE' value = 'COVEREDRATIO' } } } } -
生成模块化报告
// 为每个模块生成独立的 HTML 报告 tasks.withType<JacocoReport>().configureEach { reports { html.outputLocation.set( layout.buildDirectory.dir("reports/jacoco/${project.name}") ) } }
这样配置后,您将获得:
- 每个模块独立的覆盖率报告
- 整个项目的聚合覆盖率报告
- 可定制的排除规则
- 支持不同构建变体的配置
- CI/CD 集成能力
更多推荐


所有评论(0)