Android - Jacoco does not generate xml report properly - android

We've integrated JaCoCo plugin to our Android project for measuring the code coverage via SonarQube. However, it could not be measured. Because SonarQube expects coverage report as an XML format. We add a custom jacocoTestReport task to build.gradle(module). It generates an xml file but it does not contain any coverage information. Have you encountered such a problem? If you had, do you have any solution?
build.gradle(root)
classpath "org.jacoco:org.jacoco.core:0.8.5"
build.gradle(module)
apply plugin: 'jacoco'
...
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
getSourceDirectories().setFrom(files([mainSrc]))
getClassDirectories().setFrom(files([debugTree]))
getExecutionData().setFrom(fileTree(dir: "$buildDir", includes: [
"jacoco/testDebugUnitTest.exec",
"outputs/code-coverage/connected/*coverage.ec"
]))
Output:

Baseline: AGP 7.1.2 + , jacoco 0.88, hilt 2.42, many you have to update the jacoco.gradle file.
apply plugin: 'jacoco'
jacoco {
toolVersion = '0.8.7'
}
task jacocoTestReport { self ->
build.dependsOn self
}
//Use this method to generate the HTML and XML coverage report files for
Unit and UI test cases
android.testVariants.all {
def variant = it.testedVariant
def variantName = variant.name.capitalize()
def unitTestTask = "test${variantName}UnitTest"
def androidTestCoverageTask = "create${variantName}CoverageReport"
tasks.create(name: "jacoco${name}TestReport", type: JacocoReport)
{ self ->
group = 'Reporting'
description = "Generates Jacoco coverage reports on the
${variant.name} variant"
reports {
xml.enabled(true)
html.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/${variant.name}",
excludes: excludes)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = "${project.projectDir}/src/main/java"
sourceDirectories.setFrom(project.files(variantSourceSets))
def androidTestsData = fileTree(dir:
"${buildDir}/outputs/code_coverage/${variant.name}AndroidTest/connected/",
includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest/test
${variantName}UnitTest.exec",
androidTestsData
]))
dependsOn unitTestTask, androidTestCoverageTask
jacocoTestReport.dependsOn self
}
}
Due to AGP being updated below path is changed in the build directory.
//Replace this code
executionData.from = fileTree(dir: buildDir, includes: [
"jacoco/test${variantName}UnitTest.exec",
"/outputs/code_coverage/${variant.name}AndroidTest/connected/*coverage.ec"
])
//To this code
def androidTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/${variant.name}AndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest/test${variantName}UnitTest.exec",
androidTestsData
]))

What is your gradle version? It seems there are some issues with gradle 6.4+
https://github.com/gradle/gradle/issues/14132

After some googling, I've finally solved this problem. Source and class directories' paths were incorrect. Correct paths should be like this:
def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
excludes: fileFilter)
def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
excludes: fileFilter)
classDirectories.setFrom(files([
javaClasses,
kotlinClasses
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
Regards,

Related

Exclude databinding package from jacoco report

In my jacoco report I see some weird databinding package.
I tried adding following to my build.gradle to exclude it:
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: '**/*databinding/**/*.*')
}))
}
classDirectories = fileTree(
dir: "${project.buildDir}",
excludes: ['**/*databinding/**/*.*']
)
}
}
}
It seems that they don't work. Is there a way to remove this package/folder from my report?
you can define much extensive filefilter
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
// 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$*.*',
// adapters generated by moshi
'**/*JsonAdapter.*',
]
def javaTree = fileTree(dir: "${project.buildDir}/intermediates/javac/$sourceName/classes", excludes: fileFilter)
def kotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/$sourceName", excludes: fileFilter)
classDirectories.from = files([javaTree], [kotlinTree])
reference - https://medium.com/nerd-for-tech/setup-jacoco-code-coverage-with-your-multimodule-android-app-kotlin-a0f82573a1
Your exclusion is almost correct, here is the right one:
'**/databinding/*'
This is because the databinding build files reside in
app\build\intermediates\javac\[buildVariantName]\classes\[your\package\name]\databinding
I can't seem to find the docs right now, but i think your exclusion databinding/**/*.* is pointing to any file in a subdirectory of databinding and not to the files in the directory itself

SonarQube works only if with one android instrumentation test max

I followed this blog: here to setup SonarQube and Jacoco for my Android app. I want to get the code coverage of unit tests and instrumentation tests. Unit tests work fine and if I have one instrumentation test and it gets reported too. But as soon as I add another instrumentation test none of the instrumentation tests are reported.
This is my sonarQube.gradle:
apply plugin: "org.sonarqube"
sonarqube {
properties {
property "sonar.host.url", "[...]"
property "sonar.projectKey", projectKey
property "sonar.projectName", projectName
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"
println "itReportPath: ${fileTree(dir: project.projectDir, includes: ["**/*.ec"])}"
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"
And this is my jacoco.gradle:
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.6"
// Custom reports directory can be specfied like this:
// reportsDir = file("$buildDir/customJacocoReportDir")
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
// see related issue https://github.com/gradle/gradle/issues/5184#issuecomment-457865951
}
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: "${unitTestTask}Coverage", 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/${variantName}AndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/jacoco/${unitTestTask}.exec",
androidTestsData
]))
}
}
}
The linked blog mentions that you have to use gradle 6.3, but I get the described behaviour even when using gradle 6.3.
How can I resolve this issue?

SonarQube Code Coverage Could not account for Kotlin files on an Android Project

Below is my sonarqube properties snippet:
sonarqube {
properties{
property "sonar.junit.reportPaths", "build/test-results/testDebugUnitTest/*.xml"
property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacocoTestReport.xml"
}
}
Jacoco configuration and properties are working fine, how did I confirm this?
I created a java class and wrote a unit test for it, sonarqube recognises this and recorded it as part of the Code Coverage, while it basically ignore all the Kotlin file test.
I went ahead to change a kotlin file to java and also write a UnitTest for it and yes, it got recorgnised and add as part of the Code Coverage, again, the kotlin file tests were ignored.
Below is my Jacoco.gradle by the way:
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'
])
After so much research and debugging, and also with info's from #LarryX answer above, I was able to play around the classes as he said, below is my working Jacoco.gradle file.
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: "${unitTestTask}Coverage", 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/${variantName}AndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/jacoco/${unitTestTask}.exec",
androidTestsData
]))
}
}
}
Take note of :
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
]))
Also, take note of project.afterEvaluate I'm not sure this is relevant but cause I think you must have run and generated your test before this section runs anyways.
This solution also works for a library if not an actual android application.
And then my sonarqube.gradle file below.
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"
Finally my 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
}
Please check jacoco config for java + kotlin project:
http://vgaidarji.me/blog/2017/12/20/how-to-configure-jacoco-for-kotlin-and-java-project/
Key is to include kotlin directories in sourceDirectories and classeDirectories

Jacoco can't find source files despite setting source directory in build.gradle

I want to get line coverage in my source files for my test coverage results. The test results in the generated table have columns for missed lines, and even for missed control flows. According to stuff I've read, this means if the source files are found, I will be able to get line coverage displayed in my source files.
Currently, I have unit test results and android test results in separate reports. The android test results say "Source file "com/example/helloworld/KeyboardUtil.kt" was not found during generation of report."
This is the relevant part of the gradle.
jacoco {
toolVersion = jacoco_version
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports {
xml.enabled = true
html.enabled = true
csv.enabled = true
}
def fileFilter = ['android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/BR.*',
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/*_MembersInjector.class',
'**/Dagger*Component.class',
'**/Dagger*Component$Builder.class',
'**/*Module_*Factory.class',
'**/di/module/*',
'**/*_Factory*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*Hilt*.*',
'**/*MapperImpl*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*Component*.*',
'**/*BR*.*',
'**/Manifest*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*Hilt*.*',
'**/*MembersInjector*.*',
'**/*_MembersInjector.class',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
'**/*$Result.*',
'**/*$Result$*.*']
def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter)
def mainSrc = fileTree(dir: "${projectDir}/src/main/java", excludes: fileFilter)
getSourceDirectories().setFrom(files([mainSrc]))
getClassDirectories().setFrom(files([debugTree]))
def unitTestsData = "$project.buildDir/jacoco/testDebugUnitTest.exec"
def androidTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/debugAndroidTest/connected/",
includes: ["**/*.ec"])
getExecutionData().setFrom(files([unitTestsData, androidTestsData]))
}
I believe the problem to be these lines:
def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter)
def mainSrc = fileTree(dir: "${projectDir}/src/main/java", excludes: fileFilter)
getSourceDirectories().setFrom(files([mainSrc]))
getClassDirectories().setFrom(files([debugTree]))
The main src directory is correct, but in the report, the path to the result is "app > com.example.helloworld > KeyboardUtil" when it should be "app > src > main > java > com.example.helloworld > KeyboardUtil"
Not sure how to fix it.

Global code coverage in multi-module Android project: merge code coverage reports (Unit & UI tests)

I have an Android app which is made up of 2 modules:
App - UI
Submodule - has most of the business logic
For each of them I have a gradle task to validate code coverage:
App: UI Code coverage (Espresso)
Submodule: Unit tests code coverage
As a requirement for the client, I need to merge those two reports to get the overall/global code coverage of the app.
Note: I'm using Gradle version 3.1.2.
App Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "uiTest"
...
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
debuggable true
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
matchingFallbacks = ['debug']
}
// TESTS
// unitTest will be used to run unit tests.
unitTest.initWith(buildTypes.debug) //Beware the buildType this depends on MUST be above on the gradle file
unitTest {
applicationIdSuffix ".unitTest"
versionNameSuffix "-unitTest"
testCoverageEnabled = true
matchingFallbacks = ['unitTest', 'debug']
}
// uiTest will be used to run ui tests.
uiTest.initWith(buildTypes.debug) //Beware the buildType this depends on MUST be above on the gradle file
uiTest {
applicationIdSuffix ".uiTest"
versionNameSuffix "-uiTest"
testCoverageEnabled = true
matchingFallbacks = ['uiTest', 'debug']
}
...
SubModule Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "uiTest"
buildTypes {
debug {
}
unitTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
uiTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
...
}
I've tried several ways, this one below indeed merges the tests.. but the coverage is not appearing correctly:
The task for creating UI Test coverage in the app:
//UI Test Coverage filtered (we need to run unit tests of App to be able to use Jacoco to filter)
task createTestReport(type: JacocoReport, dependsOn: [':app:testUnitTestUnitTest', ':app:createUiTestAndroidTestCoverageReport']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
//Utils
'**/utils/*.*',
'**/utils/**/*.*'
]
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["**/*.ec"])
def appOtherAndroidTests = fileTree(dir: "${buildDir}/outputs/androidTest-results/connected/", includes: ["**/*.ec"])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec", appAndroidTests, appOtherAndroidTests)
}
The task for creating Unit test coverage in the sub-module:
//Unit Test Coverage filtered
task createTestReport(type: JacocoReport, dependsOn: ['testUnitTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = ['**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'**/*databinding/**/*.*',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
'android/**/*.*',
'**/utils/*.*',
'**/utils/**/*.*']
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec")
}
The task for creating Global coverage in the app:
//Global Test Coverage
task createGlobalTestReport(type: JacocoReport,
dependsOn: [':app:testUnitTestUnitTest', ':app:createTestReport',
':submodule:testUnitTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
//Utils
'**/utils/*.*',
'**/utils/**/*.*'
]
// Note: **/reviews/ReviewService*.* was added as BazaarVoice cannot be mocked
//To support Java coverage on Unit tests
def debugAppTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def debugKotlinAppTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def debugSdkTree = fileTree(dir: "..//build/intermediates/classes/unitTest", excludes: fileFilter)
def debugKotlinSdkTree = fileTree(dir: "../submodule/build/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainAppSrc = "${project.projectDir}/src/main/java"
def debugAppSrc = "${project.projectDir}/src/debug/java"
def mainSdkSrc = "../submodule/src/main/java"
def debugSdkSrc = "../submodule/src/debug/java"
sourceDirectories = files([mainAppSrc, debugAppSrc,
mainSdkSrc, debugSdkSrc])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["**/*.ec"])
def sdkAndroidTests = fileTree(dir: "../submodule/build/outputs/code-coverage/connected/", includes: ["**/*.ec"])
classDirectories = files([debugAppTree, debugSdkTree,
debugKotlinAppTree, debugKotlinSdkTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec"
, "../submodule/build/jacoco/testUnitTestUnitTest.exec"
, appAndroidTests
, sdkAndroidTests
)
}
Any help would be much appreciated
I'm not sure it works with multiple build types.
Try merging it into a single build type and:
App Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "automationTest"
...
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
debuggable true
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
matchingFallbacks = ['debug']
}
// TESTS
automationTest {
applicationIdSuffix ".automationTest"
versionNameSuffix "-automationTest"
testCoverageEnabled = true
matchingFallbacks = ['automationTest', 'debug']
}
...
SubModule Gradle file:
apply plugin: 'jacoco'
android {
buildTypes {
debug {
}
automationTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
release {
initWith(buildTypes.debug)
}
...
}
The task for creating Unit test coverage in the sub-module:
task createUnitTestReport(type: JacocoReport, dependsOn: ['testAutomationTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = ['**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'**/*databinding/**/*.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*']
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/automationTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testAutomationTestUnitTest.exec")
}
The task for creating Global coverage in the app:
task createGlobalTestReport(type: JacocoReport,
dependsOn: [':app:createUiTestReport', ':submodule:createUnitTestReport']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*'
]
//To support Java coverage on Unit tests
def debugAppTree = fileTree(dir: "${buildDir}/intermediates/classes/automationTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def debugKotlinAppTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def debugSdkTree = fileTree(dir: "../submodule/build/intermediates/classes/automationTest", excludes: fileFilter)
def debugKotlinSdkTree = fileTree(dir: "../submodule/build/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def mainAppSrc = "${project.projectDir}/src/main/java"
def debugAppSrc = "${project.projectDir}/src/debug/java"
def mainSdkSrc = "../submodule/src/main/java"
def debugSdkSrc = "../submodule/src/debug/java"
sourceDirectories = files([mainAppSrc, debugAppSrc,
mainSdkSrc, debugSdkSrc])
classDirectories = files([debugAppTree, debugSdkTree,
debugKotlinAppTree, debugKotlinSdkTree])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["*.ec"])
executionData = files("${buildDir}/jacoco/testAutomationTestUnitTest.exec"
, "../submodule/build/jacoco/testAutomationTestUnitTest.exec"
, appAndroidTests
)
}
I use a plugin by palantir to create aggregate coverage reports for my different modules - com.palantir.jacoco-full-report
Basically, in your root gradle file, you need to add this in your dependencies:
classpath 'com.palantir:jacoco-coverage:0.4.0'
After that, when you run ./gradlew test jacocoFullReport a test report in build/reports/jacoco/jacocoFullReport/ that evaluates the coverage yielded by all subprojects combined is created.

Categories

Resources