英文:
Setting up jacoco for multi-module Android codebase with Gadle kotlin
问题
我目前正在为我团队构建的一个Android代码库设置Jacoco。我在Android方面没有太多经验,但之前我在一个Spring Boot代码库中设置过Jacoco,以便我可以在Sonarqube中跟踪测试覆盖率。但是在Android代码库中我遇到了一些困难。
所以目录结构看起来像这样:
MyApp/
├─ app/
│ ├─ build/
│ ├─ src/
│ ├─ build.gradle.kts
├─ buildSrc/
│ ├─ build.gradle.kts
├─ modules/
│ ├─ module1/
│ │ ├─ src/
│ │ ├─ build.gradle.kts
│ ├─ module2/
│ │ ├─ src/
│ │ ├─ build.gradle.kts
├─ build.gradle.kts
├─ gradle.properties
├─ gradlew
├─ settings.gradle.kts
我尝试在MyApp/build.gradle.kts
中添加jacoco
。
plugins {
id(Dependencies.Plugins.androidApplication) version Dependencies.Versions.androidAppplication apply false
id(Dependencies.Plugins.androidLibrary) version Dependencies.Versions.androidLibrary apply false
id(Dependencies.Plugins.hilt) version Dependencies.Versions.hilt apply false
id(Dependencies.Plugins.openApi) version Dependencies.Versions.openApi
id(Dependencies.Plugins.kotlinAndroid) version Dependencies.Versions.kotlinAndroid apply false
id(Dependencies.Plugins.sonarqube) version Dependencies.Versions.sonarqube
id("jacoco")
}
我尝试执行bash gradlew test jacocoTestReport
,但是它显示:
Task 'jacocoTestReport' not found in root project 'MyApp' and its subprojects.
根据JaCoco插件文档(https://docs.gradle.org/current/userguide/jacoco_plugin.html),我尝试在MyApp/build.gradle.kts
中添加以下行:
tasks.test {
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
}
但是输出显示类似以下内容:
Script compilation errors:
Line 12: tasks.test {
^ Unresolved reference: test
Line 13: finalizedBy(tasks.jacocoTestReport)
^ Unresolved reference: finalizedBy
我之前在一个Spring Boot项目中进行了类似的配置,它可以正常运行。但是在这里它甚至无法检测到任务jacocoTestReport
。
我做错了什么?
英文:
I am currently setting up Jacoco in an Android codebase that my team built. I don't have much experience in Android but I have set up Jacoco before in a Spring Boot codebase so that I can track the test coverage in Sonarqube. But I am having hard time doing in the Android codebase.
So the directory layout looks like this
MyApp/
├─ app/
│ ├─ build/
│ ├─ src/
│ ├─ build.gradle.kts
├─ buildSrc/
│ ├─ build.gradle.kts
├─ modules/
│ ├─ module1/
│ │ ├─ src/
│ │ ├─ build.gradle.kts
│ ├─ module2/
│ │ ├─ src/
│ │ ├─ build.gradle.kts
├─ build.gradle.kts
├─ gradle.properties
├─ gradlew
├─ settings.gradle.kts
I tried adding jacoco
in the MyApp/build.gradle.kts
.
plugins {
id(Dependencies.Plugins.androidApplication) version Dependencies.Versions.androidAppplication apply false
id(Dependencies.Plugins.androidLibrary) version Dependencies.Versions.androidLibrary apply false
id(Dependencies.Plugins.hilt) version Dependencies.Versions.hilt apply false
id(Dependencies.Plugins.openApi) version Dependencies.Versions.openApi
id(Dependencies.Plugins.kotlinAndroid) version Dependencies.Versions.kotlinAndroid apply false
id(Dependencies.Plugins.sonarqube) version Dependencies.Versions.sonarqube
id("jacoco")
}
I tried executing bash gradlew test jacocoTestReport
but it says
Task 'jacocoTestReport' not found in root project 'MyApp' and its subprojects.
I tried adding the line below in the MyApp/build.gradle.kts
, per JaCoco Plugin documentation (https://docs.gradle.org/current/userguide/jacoco_plugin.html)
tasks.test {
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
}
tasks.jacocoTestReport {
dependsOn(tasks.test) // tests are required to run before generating the report
}
but the output says something like below
Script compilation errors:
Line 12: tasks.test {
^ Unresolved reference: test
Line 13: finalizedBy(tasks.jacocoTestReport)
^ Unresolved reference: finalizedBy
I did similar configuration in a Spring Boot project before which ran normal. But here it cannot even detect task jacocoTestReport
.
What did I do wrong?
答案1
得分: 2
在传统的Java或Kotlin JVM项目中,有一个名为tasks.test
的单个测试任务。
然而,在Android项目中,没有一个单独的test
任务。相反,有多个测试任务,每个构建变体都有一个。这些测试任务的名称为test<Variant>UnitTest
。例如,你可能会有testDebugUnitTest
和testReleaseUnitTest
。
当你尝试在Android项目中访问tasks.test
时,会出现"Unresolved reference: test"错误,因为没有名为test
的任务。
类似地,finalizedBy(tasks.jacocoTestReport)
是一种指定任务依赖关系的方式,表示jacocoTestReport
任务应该在test
任务之后始终运行。然而,由于Android项目中没有test
任务,这会导致"Unresolved reference: finalizedBy"错误。
根据"How do I add plugins to subprojects based on what plugins are present?",你可能需要在根目录的build.gradle.kts
中创建一个Jacoco设置,自动为使用Android插件的每个模块应用正确的Jacoco设置。
使用Gradle DLS脚本:
import org.gradle.testing.jacoco.tasks.JacocoReport
plugins {
// your existing plugins...
id("jacoco")
}
// rest of your configurations...
subprojects { subproject ->
subproject.plugins.withId(Dependencies.Plugins.androidApplication) {
setupJacoco(subproject)
}
subproject.plugins.withId(Dependencies.Plugins.androidLibrary) {
setupJacoco(subproject)
}
}
fun setupJacoco(project: Project) {
project.tasks.register("jacocoTestReport", JacocoReport::class.java) { task ->
task.group = "Reporting"
task.description = "Generate Jacoco coverage reports after running tests."
task.reports {
xml.isEnabled = true
html.isEnabled = true
}
task.executionData.setFrom(
project.fileTree(project.buildDir).apply {
include("**/jacoco/*.exec")
}
)
task.sourceDirectories.setFrom(
project.files(
project.extensions.getByType<SourceSetContainer>().getByName("main").allSource.srcDirs
)
)
task.classDirectories.setFrom(
project.fileTree(
project.extensions.getByType<SourceSetContainer>().getByName("main").output.classesDirs
)
)
}
}
该脚本将检查构建中的每个子项目。如果子项目应用了你的IDs,比如Dependencies.Plugins.androidApplication
或Dependencies.Plugins.androidLibrary
插件,它会为该子项目添加Jacoco任务。对于每个项目,添加一个生成Jacoco报告的jacocoTestReport
任务。
需要注意的一点是,Android Gradle插件会为每个应用程序/库模块的每个构建变体创建一个test
任务。例如,如果你有debug
和release
构建类型,Android Gradle插件将创建testDebugUnitTest
和testReleaseUnitTest
任务。
也就是说,你需要运行特定的test
任务,然后再运行jacocoTestReport
,如下所示:
# 在修改build.gradle.kts后执行一次
./gradlew --refresh-dependencies
# 每次生成报告时执行以下命令
./gradlew module1:testDebugUnitTest module1:jacocoTestReport
这将运行module1
的单元测试,然后生成Jacoco报告。请注意,如果你使用产品风味,可能会有多种类型的test
任务(例如testFreeDebugUnitTest
,testPaidDebugUnitTest
等),所以根据你的项目配置相应调整命令。
英文:
In a traditional Java or Kotlin JVM project, there is a single test task, accessible via tasks.test
.
However, in an Android project, there isn't a single test
task. Instead, there are multiple test tasks, one for each build variant. The test tasks are named test<Variant>UnitTest
. For example, you might have testDebugUnitTest
and testReleaseUnitTest
.
When you try to access tasks.test
in an Android project, it results in an "Unresolved reference: test" error because there is no task with the name test
.
Similarly, finalizedBy(tasks.jacocoTestReport)
is a way to specify task dependencies, saying that the jacocoTestReport
task should always be run after the test
task. However, since there is no test
task in an Android project, this results in an "Unresolved reference: finalizedBy
" error.
Following "How do I add plugins to subprojects based on what plugins are present?", you might need to create a Jacoco setup in your root build.gradle.kts
that automatically applies the correct Jacoco setup for each module that uses the Android plugin.
Using a Gradle DLS script:
import org.gradle.testing.jacoco.tasks.JacocoReport
plugins {
// your existing plugins...
id("jacoco")
}
// rest of your configurations...
subprojects { subproject ->
subproject.plugins.withId(Dependencies.Plugins.androidApplication) {
setupJacoco(subproject)
}
subproject.plugins.withId(Dependencies.Plugins.androidLibrary) {
setupJacoco(subproject)
}
}
fun setupJacoco(project: Project) {
project.tasks.register("jacocoTestReport", JacocoReport::class.java) { task ->
task.group = "Reporting"
task.description = "Generate Jacoco coverage reports after running tests."
task.reports {
xml.isEnabled = true
html.isEnabled = true
}
task.executionData.setFrom(
project.fileTree(project.buildDir).apply {
include("**/jacoco/*.exec")
}
)
task.sourceDirectories.setFrom(
project.files(
project.extensions.getByType<SourceSetContainer>().getByName("main").allSource.srcDirs
)
)
task.classDirectories.setFrom(
project.fileTree(
project.extensions.getByType<SourceSetContainer>().getByName("main").output.classesDirs
)
)
}
}
That script will check every subproject in the build. If a subproject applies your IDs, like Dependencies.Plugins.androidApplication
or Dependencies.Plugins.androidLibrary
plugin, it adds Jacoco tasks for that subproject.
For each of these projects, add a jacocoTestReport
task which generates the Jacoco reports.
One important thing to note is that the Android Gradle plugin creates a test
task for each build variant of the app/library module. For example, if you have debug
and release
build types, the Android Gradle plugin will create testDebugUnitTest
and testReleaseUnitTest
tasks.
Meaning, you will need to run the specific test
task and then jacocoTestReport
like this:
# Do this once, after making changes to your build.gradle.kts
./gradlew --refresh-dependencies
# Then, each time you want to generate the report
./gradlew module1:testDebugUnitTest module1:jacocoTestReport
That will run the unit tests for module1
and then generate the Jacoco report. Note that if you are using product flavors, you might have more than two types of test
tasks (e.g., testFreeDebugUnitTest
, testPaidDebugUnitTest
, etc.), so do adjust your command accordingly to fit your project configuration.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论