SonarQube:使用 JaCoCo 的多模块 gradle 项目的覆盖范围不完整
SonarQube: Coverage incomplete on multimodule gradle project with JaCoCo
我正在构建一个 SonarQube 6.2 服务器,它已经在分析我的 Java 8/Gradle 3.3 项目。将 JaCoCo 添加到多模块 gradle 项目时,我意识到 SonarQube 正在 "per-module" 基础上测量代码覆盖率:
如果 class 位于模块 A
中,而此 class 的测试位于模块 B
中,SonarQube 认为 class 是未涵盖.
我想测量所有模块的代码覆盖率,而不是基于每个模块。我该如何实现?
有很多类似的问题,但没有有用的答案,尽管这种情况对我来说似乎很常见。例如,Jenkins 默认会这样做。
我决定建立一个 blueprint on github 来澄清这个问题。
主要build.gradle
包括
plugins { id "org.sonarqube" version "2.2.1" }
subprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
repositories { mavenCentral() }
dependencies { testCompile "junit:junit:4.12" }
}
modA/build.gradle
为空。
它包含 3 个 class:TestedInModA
、TestedInModATest
和 TestedViaModB
。
modB/build.gradle
只是声明对modA
的依赖:
dependencies { compile project(':modA') }
它只包含一个 class: TestedViaModBTest
,测试 class TestedViaModB
位于 modA
.
我的(私有)Jenkins 实例显示包含的两个 classes 的覆盖率为 100%,而 SonarQube 表示只有 class TestedInModA
(在其自己的模块中测试)是覆盖。
如何修改构建过程以在 SonarQube 中查看 "cross-module coverage"?
我很乐意更新我的项目,以便未来访问此问题的人可以找到一个工作示例。
我的工作解决方案(感谢@Godin)
将以下内容添加到 subprojects
闭包中
tasks.withType(Test) {
// redirect all coverage data to one file
// ... needs cleaning the data prior to the build to avoid accumulating coverage data of different runs.
// see `task cleanJacoco`
jacoco {
destinationFile = file("$rootProject.buildDir/jacoco/test.exec")
}
}
添加
task cleanJacoco(dependsOn: 'clean') { delete "$buildDir/jacoco" }
subprojects
闭包之外。
当您执行构建 JaCoCo Gradle 插件将生成 modA/build/jacoco/test.exec
和 modB/build/jacoco/test.exe
,其中分别包含有关在 modA
和 modB
中执行测试的信息。 SonarQube 单独执行模块分析,因此在分析文件 TestedViaModB
的 modA
期间,它只会看到 modA/build/jacoco/test.exec
.
跨越边界的最常见技巧 - 是将所有覆盖范围信息收集到一个位置。这可以用
JaCoCo Gralde 插件
要么通过更改位置 - 请参阅 destinationFile
and destPath
(由于信息已附加到 exec
文件,请不要忘记在构建之前删除此单个位置,否则它将积累来自不同构建而不仅仅是来自不同模块的信息),
通过将所有文件合并为一个文件 - 参见 JacocoMerge
task. And then specify this single location to SonarQube as sonar.jacoco.reportPath
。
另一个技巧:SonarQube 6.2 with Java Plugin 4.4 supports property sonar.jacoco.reportPaths
允许指定多个位置。
如果您对 sonar.jacoco.reportPaths
的解决方案感兴趣(请参阅 Godin 的回答),请查看此 gradle 代码:
tasks.getByName('sonarqube') {
doFirst {
// lazy initialize the property to collect all coverage files
def jacocoFilesFromSubprojects = subprojects.findAll {it.plugins.hasPlugin('jacoco')}
.collect {it.tasks.withType(Test)}.flatten().collect {it.jacoco.destinationFile}
sonarqube.properties {
property "sonar.jacoco.reportPaths", jacocoFilesFromSubprojects.join(',')
}
}
}
这将收集所有覆盖二进制文件并将它们设置为以逗号分隔的列表到声纳 属性。考虑应用了 jacoco 并配置了它们的 jacoco 目标文件的子项目的所有测试任务。
我正在构建一个 SonarQube 6.2 服务器,它已经在分析我的 Java 8/Gradle 3.3 项目。将 JaCoCo 添加到多模块 gradle 项目时,我意识到 SonarQube 正在 "per-module" 基础上测量代码覆盖率:
如果 class 位于模块 A
中,而此 class 的测试位于模块 B
中,SonarQube 认为 class 是未涵盖.
我想测量所有模块的代码覆盖率,而不是基于每个模块。我该如何实现?
有很多类似的问题,但没有有用的答案,尽管这种情况对我来说似乎很常见。例如,Jenkins 默认会这样做。
我决定建立一个 blueprint on github 来澄清这个问题。
主要build.gradle
包括
plugins { id "org.sonarqube" version "2.2.1" }
subprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
repositories { mavenCentral() }
dependencies { testCompile "junit:junit:4.12" }
}
modA/build.gradle
为空。
它包含 3 个 class:TestedInModA
、TestedInModATest
和 TestedViaModB
。
modB/build.gradle
只是声明对modA
的依赖:
dependencies { compile project(':modA') }
它只包含一个 class: TestedViaModBTest
,测试 class TestedViaModB
位于 modA
.
我的(私有)Jenkins 实例显示包含的两个 classes 的覆盖率为 100%,而 SonarQube 表示只有 class TestedInModA
(在其自己的模块中测试)是覆盖。
如何修改构建过程以在 SonarQube 中查看 "cross-module coverage"?
我很乐意更新我的项目,以便未来访问此问题的人可以找到一个工作示例。
我的工作解决方案(感谢@Godin)
将以下内容添加到
subprojects
闭包中tasks.withType(Test) { // redirect all coverage data to one file // ... needs cleaning the data prior to the build to avoid accumulating coverage data of different runs. // see `task cleanJacoco` jacoco { destinationFile = file("$rootProject.buildDir/jacoco/test.exec") } }
添加
task cleanJacoco(dependsOn: 'clean') { delete "$buildDir/jacoco" }
subprojects
闭包之外。
当您执行构建 JaCoCo Gradle 插件将生成 modA/build/jacoco/test.exec
和 modB/build/jacoco/test.exe
,其中分别包含有关在 modA
和 modB
中执行测试的信息。 SonarQube 单独执行模块分析,因此在分析文件 TestedViaModB
的 modA
期间,它只会看到 modA/build/jacoco/test.exec
.
跨越边界的最常见技巧 - 是将所有覆盖范围信息收集到一个位置。这可以用 JaCoCo Gralde 插件
要么通过更改位置 - 请参阅
destinationFile
anddestPath
(由于信息已附加到exec
文件,请不要忘记在构建之前删除此单个位置,否则它将积累来自不同构建而不仅仅是来自不同模块的信息),通过将所有文件合并为一个文件 - 参见
JacocoMerge
task. And then specify this single location to SonarQube assonar.jacoco.reportPath
。
另一个技巧:SonarQube 6.2 with Java Plugin 4.4 supports property sonar.jacoco.reportPaths
允许指定多个位置。
如果您对 sonar.jacoco.reportPaths
的解决方案感兴趣(请参阅 Godin 的回答),请查看此 gradle 代码:
tasks.getByName('sonarqube') {
doFirst {
// lazy initialize the property to collect all coverage files
def jacocoFilesFromSubprojects = subprojects.findAll {it.plugins.hasPlugin('jacoco')}
.collect {it.tasks.withType(Test)}.flatten().collect {it.jacoco.destinationFile}
sonarqube.properties {
property "sonar.jacoco.reportPaths", jacocoFilesFromSubprojects.join(',')
}
}
}
这将收集所有覆盖二进制文件并将它们设置为以逗号分隔的列表到声纳 属性。考虑应用了 jacoco 并配置了它们的 jacoco 目标文件的子项目的所有测试任务。