SonarQube 代码覆盖率无法解释 Android 项目中的 Kotlin 文件
Posted
技术标签:
【中文标题】SonarQube 代码覆盖率无法解释 Android 项目中的 Kotlin 文件【英文标题】:SonarQube Code Coverage Could not account for Kotlin files on an Android Project 【发布时间】:2021-09-22 00:04:13 【问题描述】:下面是我的 sonarqube 属性 sn-p:
sonarqube
properties
property "sonar.junit.reportPaths", "build/test-results/testDebugUnitTest/*.xml"
property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacocoTestReport.xml"
Jacoco 配置和属性工作正常,我如何确认这一点? 我创建了一个 java 类并为它编写了一个单元测试,sonarqube 识别了这一点并将其记录为代码覆盖率的一部分,而它基本上忽略了所有 Kotlin 文件测试。 我继续将 kotlin 文件更改为 java 并为它编写了一个 UnitTest,是的,它被重新识别并添加为代码覆盖率的一部分,再次忽略了 kotlin 文件测试。
下面是我的 Jacoco.gradle:
apply plugin: 'jacoco'
ext
coverageExclusions = [
'**/*Activity*.*',
'**/*Fragment*.*',
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
]
jacoco
toolVersion = '0.8.6'
reportsDir = file("$buildDir/reports")
tasks.withType(Test)
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
tasks.withType(Test)
finalizedBy jacocoTestReport // report is always generated after tests run
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest'])
group = "Reporting"
description = "Generate Jacoco coverage reports for Debug build"
reports
xml.enabled(true)
html.enabled(true)
xml.destination(file("build/reports/jacocoTestReport.xml"))
def debugTree = fileTree(dir: "$buildDir/intermediates/javac/debug/classes", excludes: coverageExclusions)
def mainSrc = "/src/main/java"
additionalSourceDirs.from = files(mainSrc)
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTree])
executionData.from = fileTree(dir: project.buildDir, includes: [
'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec'
])
【问题讨论】:
运行测试时,它会生成任何 .exec 文件吗?多少?它应该在 build/jacoco 目录中 是的,只有一个文件 testDebugUnitTest.exec 【参考方案1】:请检查 java + kotlin 项目的 jacoco 配置:
http://vgaidarji.me/blog/2017/12/20/how-to-configure-jacoco-for-kotlin-and-java-project/
关键是在 sourceDirectories 和 classeDirectories 中包含 kotlin 目录
【讨论】:
我不确定这一点,但我能够通过玩弄你所说的来让它工作,现在它工作了,我也会把它作为答案发布。【参考方案2】:经过这么多的研究和调试,以及上面@LarryX 回答的信息,我能够按照他所说的那样玩这些课程,下面是我的工作Jacoco.gradle
文件。
apply plugin: 'jacoco'
jacoco
toolVersion = "0.8.4"
tasks.withType(Test)
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
project.afterEvaluate
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants')
.all variant ->
def variantName = variant.name
def unitTestTask = "test$variantName.capitalize()UnitTest"
def androidTestCoverageTask = "create$variantName.capitalize()CoverageReport"
tasks.create(name: "$unitTestTaskCoverage", type: JacocoReport, dependsOn: [
"$unitTestTask",
"$androidTestCoverageTask"
])
group = "Reporting"
description = "Generate Jacoco coverage reports for the $variantName.capitalize() build"
reports
html.enabled(true)
xml.enabled(true)
csv.enabled(true)
def excludes = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
// butterKnife
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
// dagger
'**/*_MembersInjector.class',
'**/Dagger*Component.class',
'**/Dagger*Component$Builder.class',
'**/*Module_*Factory.class',
'**/di/module/*',
'**/*_Factory*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*Hilt*.*',
// kotlin
'**/*MapperImpl*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*Component*.*',
'**/*BR*.*',
'**/Manifest*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*Hilt*.*',
'**/*MembersInjector*.*',
'**/*_MembersInjector.class',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
// sealed and data classes
'**/*$Result.*',
'**/*$Result$*.*'
]
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: excludes)
def kotlinClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/$variantName",
excludes: excludes)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect it.path .flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
def androidTestsData = fileTree(dir: "$buildDir/outputs/code_coverage/$variantNameAndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/jacoco/$unitTestTask.exec",
androidTestsData
]))
注意:
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: excludes)
def kotlinClasses = fileTree(dir: "$buildDir/tmp/kotlin-classes/$variantName",
excludes: excludes)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
另外,请注意project.afterEvaluate
我不确定这是否相关,但因为我认为您必须在本节运行之前运行并生成您的测试。
如果不是实际的 android 应用程序,此解决方案也适用于库。
然后是我的sonarqube.gradle
文件。
apply plugin: "org.sonarqube"
sonarqube
properties
property "sonar.host.url", "http://localhost:9000/"
property "sonar.projectKey", "fair"
property "sonar.projectName", "fair"
property "sonar.login", "de9d79fe4d3aef9879567afc91a2ce465038d9be"
property "sonar.projectVersion", "$android.defaultConfig.versionName"
property "sonar.junit.reportsPath", "build/test-results/testDebugUnitTest"
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.android.lint.report", "build/reports/lint-results.xml"
property "sonar.jacoco.reportPaths", "build/jacoco/testDebugUnitTest.exec"
property "sonar.jacoco.itReportPath", fileTree(dir: project.projectDir, includes: ["**/*.ec"])
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/testDebugUnitTestCoverage/testDebugUnitTestCoverage.xml"
tasks.sonarqube.dependsOn ":app:testDebugUnitTestCoverage"
最后是我的build.gradle(project)
buildscript
repositories
google()
jcenter()
dependencies
classpath "com.android.tools.build:gradle:4.1.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
classpath "org.jacoco:org.jacoco.core:0.8.7"
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3")
allprojects
repositories
google()
jcenter()
task clean(type: Delete)
delete rootProject.buildDir
【讨论】:
以上是关于SonarQube 代码覆盖率无法解释 Android 项目中的 Kotlin 文件的主要内容,如果未能解决你的问题,请参考以下文章