Skip to content

Instantly share code, notes, and snippets.

@mrsasha
Created July 13, 2018 08:25
Show Gist options
  • Save mrsasha/384a19f97cdeba5b5c2ea55f930fccd4 to your computer and use it in GitHub Desktop.
Save mrsasha/384a19f97cdeba5b5c2ea55f930fccd4 to your computer and use it in GitHub Desktop.
Jacoco setup for calculating kotlin test coverage (single module)

Jacoco setup for calculating kotlin test coverage (single module)

put the jacoco.gradle file below somewhere in your app repo (like scripts folder), and then add apply from: '../scripts/jacoco.gradle' to your module gradle file.

This will add additional gradle build tasks to your app, in the form of "testFlavourUnitTestCoverage" that you can run manually (e.g. testStagingDebugUnitTestCoverage) to generate the coverage.

You can edit def excludes definition to further exclude classes you don't want to generate the coverage for. If you have source in other folders beyond those specified in def coverageSourceDirs, add them.

This will generate both exec file used by external code quality analyzers (like Sonarqube), in the build/jacoco folder, and XML and HTML reports, in the build/reports/jacoco/flavourName folder.

You can then click through the links in the report and arrive at the annotated code to see exactly which lines are not covered by tests.

apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.1"
}
project.afterEvaluate {
// Grab all build types and product flavors
def buildTypes = android.buildTypes.collect { type -> type.name }
def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
// When no product flavors defined, use empty
if (!productFlavors) productFlavors.add('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
def sourceName, sourcePath
if (!productFlavorName) {
sourceName = sourcePath = "${buildTypeName}"
} else {
sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
sourcePath = "${productFlavorName}/${buildTypeName}"
}
def testTaskName = "test${sourceName.capitalize()}UnitTest"
// Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest'
task "${testTaskName}Coverage" (type:JacocoReport, dependsOn: "$testTaskName") {
group = "Reporting"
description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."
def excludes = [
'**/R.class',
'**/R$*.class',
'**/Manifest*.*',
'android/**/*.*',
'**/BuildConfig.*',
'**/*$ViewBinder*.*',
'**/*$ViewInjector*.*',
'**/Lambda$*.class',
'**/Lambda.class',
'**/*Lambda.class',
'**/*Lambda*.class'
]
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${sourcePath}",
excludes: excludes
) + fileTree(
dir: "${project.buildDir}/tmp/kotlin-classes/${sourceName}",
excludes: excludes
)
def coverageSourceDirs = [
"src/main/java",
"src/$productFlavorName/java",
"src/$buildTypeName/java"
]
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
}
}
}
@suchiz
Copy link

suchiz commented May 26, 2021

Hello,
I tried your script by calling "gradlew clean testDebugUnitTestjacocoTaskReport" but in this way jacocoTaskReport isnt a valid command.
So I tried "gradlew clean testDebugUnitTest" only but it dosent generate a report.xml file. Have you any suggestions on what could happened ?

I did add "apply from: 'path/jacoco.gradle'" in my build.gradle and it syncs well.

Gradle: 4.1.3
Jacoco: 0.8.4

Thank you very much.

@jrgleason
Copy link

When I run I get...

> Cannot set the value of read-only property 'classDirectories' for task ':app:testDebugUnitTestCoverage' of type org.gradle.testing.jacoco.tasks.JacocoReport.

@jrgleason
Copy link

So I can run the command with this fork update...

https://gist.github.com/jrgleason/6da9d617d5d99d6345fa8e16b0177b96

But I don't see the reports getting generated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment