jacocoRootReport任务需在根项目显式声明并配置:聚合所有子模块的*.exec文件(推荐fileTree扫描),统一设置sourceDirectories和classDirectories(含Kotlin路径),排除规则须在聚合任务内定义,且该报告不支持增量覆盖与阈值校验。

jacocoRootReport 任务必须显式声明
Gradle 的 jacoco 插件默认只在每个子模块生成独立的 jacocoTestReport,不会自动聚合。不手动定义聚合任务,根目录下永远不会有汇总报告。
必须在根项目的 build.gradle(或 build.gradle.kts)里声明一个类型为 JacocoReport 的任务,并明确指定它要聚合哪些子模块的数据:
-
executionData指向所有子模块的*.exec文件,常见路径是subprojects*.test.reports.jacoco.test.exec或更稳妥地用fileTree扫描**/build/jacoco/test.exec -
sourceDirectories和classDirectories必须统一指向根项目源码和编译输出,不能沿用子模块各自的路径,否则类名匹配失败,覆盖率显示为 0% - 如果子模块用了 Kotlin,
classDirectories还得额外包含**/classes/kotlin/main
多模块 exec 文件路径容易错配
JaCoCo 覆盖率数据写入的 .exec 文件位置取决于测试任务的配置。很多项目误以为只要子模块都 apply 'jacoco',就能自动被聚合,结果发现 jacocoRootReport 报告里全是空数据——根本原因是找不到有效的 .exec 文件。
常见错误路径模式:
- Gradle 7+ 默认路径:
build/jacoco/test.exec(由test任务自动生成) - 手动配置过
jacoco { destinationFile = ... }的模块,路径可能不一致,必须统一或显式收集 - Android 模块默认生成的是
coverage.ec,路径在build/outputs/code-coverage/connected/,不能直接混进 Java 模块的聚合流程
建议做法:在根项目里用 fileTree(dir: '.', include: '**/build/jacoco/test.exec') 安全扫描,比硬编码路径更可靠。
exclude 规则在聚合层才真正生效
你在子模块里写的 jacoco { excludes = ['**/dto/**'] } 只影响该模块的本地报告,对聚合报告无效。聚合时所有 class 都会被重新加载比对,排除逻辑必须放在 jacocoRootReport 任务内部。
正确写法示例(Groovy):
jacocoRootReport {
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
'**/dto/**',
'**/config/**',
'**/*Test.class',
'**/R.class',
'**/BuildConfig.class'
])
}))
}
}
注意:Kotlin DSL 中需用 setFrom(...) + map 替代 collect,语法略有差异;另外 exclude 是文件路径匹配,不是包名匹配,别写成 'com.example.dto.**'。
聚合报告不显示增量覆盖,也没法驱动门禁
jacocoRootReport 只是把所有 .exec 数据合并后算一次总覆盖率,它不感知 Git 提交范围,也不支持 check 块里的阈值校验。想实现“本次 PR 修改的代码行覆盖率 ≥80%”,必须额外引入 jacocoTestCoverageVerification 并配合 executionData 精确控制输入源。
更关键的是:聚合报告生成后,jacocoTestCoverageVerification 默认仍只检查单模块的 test.exec,不会自动切换到聚合后的数据。你得手动让 verification 任务依赖 jacocoRootReport,并重设 executionData 指向聚合输出——这点几乎没人提,但漏掉就等于门禁形同虚设。

















