如何将Android多模块单元测试报告合并到单一目录?
多模块Android单元测试报告合并优化方案
问题背景
为便于将测试报告复制到CI环境,需要把多模块的Android单元测试报告合并到单一目录。由于Gradle的test-report-aggregation插件对Android项目无效,自行编写了项目级build.gradle.kts代码,但存在缺陷:当仅执行app:debugUnitTest时,会自动运行其他模块(如module1)的测试,希望仅合并实际被执行的测试报告。
原项目级build.gradle.kts代码
val debugTestReport: TaskProvider<TestReport> = tasks.register<TestReport>("debugTestReport") { destinationDirectory.set(file("$buildDir/reports/debug")) // Combine all 'test' task results into a single HTML report @Suppress("DEPRECATION") reportOn(subprojects.map { it.tasks.withType(Test::class).filter { test -> test.name == "testDebugUnitTest" } } ) } val releaseTestReport: TaskProvider<TestReport> = tasks.register<TestReport>("releaseTestReport") { destinationDirectory.set(file("$buildDir/reports/release")) // Combine all 'test' task results into a single HTML report @Suppress("DEPRECATION") reportOn(subprojects.map { it.tasks.withType(Test::class).filter { test -> test.name == "testReleaseUnitTest" } }) } tasks.register<TestReport>("testReport") { dependsOn(debugTestReport, releaseTestReport) } subprojects { project.plugins.configure(project = project) // Don't create the reports if we're running from the IDE, ideally we'd only generate the // reports for complete test runs anyway if (properties.containsKey("android.injected.invoked.from.ide")) return@subprojects // Check that we're not running verifyPaparazziDebug or recordPaparazziDebug if (gradle.startParameter.taskNames.none { it.matches(Regex(".*[v|r][a-z]*P[a-z]*D.*")) }) { tasks.withType<Test> { val reports = if (name == "testDebugUnitTest") debugTestReport else releaseTestReport finalizedBy(reports) ignoreFailures = true testLogging { events("failed") } exclude("**/design/snapshot/**") } } }
优化方案
核心思路是:让合并报告任务仅依赖实际被执行的测试任务,通过Gradle任务图的执行状态动态收集需要合并的测试结果,避免触发未指定的模块测试。
优化后的项目级build.gradle.kts代码
// 注册debug测试报告合并任务,初始不绑定测试任务 val debugTestReport = tasks.register<TestReport>("debugTestReport") { destinationDirectory.set(file("$buildDir/reports/debug")) } // 注册release测试报告合并任务,初始不绑定测试任务 val releaseTestReport = tasks.register<TestReport>("releaseTestReport") { destinationDirectory.set(file("$buildDir/reports/release")) } // 统一触发合并任务的入口 tasks.register("testReport") { dependsOn(debugTestReport, releaseTestReport) } subprojects { // IDE环境下跳过报告合并逻辑 if (properties.containsKey("android.injected.invoked.from.ide")) return@subprojects // 排除Paparazzi相关任务场景 if (gradle.startParameter.taskNames.any { it.matches(Regex(".*[v|r][a-z]*P[a-z]*D.*")) }) return@subprojects tasks.withType<Test>().configureEach { val currentTestTask = this val targetReportTask = if (name == "testDebugUnitTest") debugTestReport else releaseTestReport // 监听任务图就绪状态,仅当当前测试任务会被执行时,才关联合并报告任务 gradle.taskGraph.whenReady { graph -> if (graph.hasTask(currentTestTask)) { targetReportTask.configure { @Suppress("DEPRECATION") reportOn(currentTestTask) } // 测试任务执行完成后触发对应合并报告任务 currentTestTask.finalizedBy(targetReportTask) } } ignoreFailures = true testLogging { events("failed") } exclude("**/design/snapshot/**") } }
关键改进点
- 动态关联任务:通过
gradle.taskGraph.whenReady监听任务执行计划,只有当测试任务确实会被执行时,才将其加入合并报告的数据源,并绑定finalizedBy关系,避免触发未指定的模块测试。 - 移除固定依赖:原代码中
reportOn直接绑定所有子模块的测试任务,导致执行单个模块测试时会触发全量测试,优化后仅关联实际运行的测试任务。 - 逻辑简化:简化子模块任务配置逻辑,确保只有必要的任务参与报告合并流程。
验证方式
- 执行
./gradlew app:debugUnitTest:仅运行app模块的debug单元测试,合并报告仅包含app模块的测试结果,不会触发其他模块测试。 - 执行
./gradlew debugUnitTest:运行所有模块的debug单元测试,合并报告包含所有模块的测试结果。 - 执行
./gradlew testReport:触发debug和release的报告合并(前提是对应测试任务已执行,或会自动触发执行)。
内容的提问来源于stack exchange,提问作者Barry Irvine
相关产品推荐
相关产品推荐

