Introduction
Here’s the description of JaCoCo from the official website:
JaCoCo is a free code coverage library for Java, which has been created by the EclEmma team based on the lessons learned from using and integration existing libraries for many years.
There are many articles which show how to configure JaCoCo
on a Java
project.
But there aren’t many articles how to do that on a Kotlin
or Java + Kotlin
project.
It’s quite an important topic, as many developers start migrating their projects from Java to Kotlin
(especially in Android
world).
Having no coverage upsets people a lot.
I have spent some time on this configuration and turns out that configuration is very simple.
JaCoCo configuration on Kotlin/Java project
Basic configuration of JaCoCo
plugin using Gradle
can be found in Gradle JaCoCo userguide.
Nothing interesting here and configuring it on Java project should be very straightforward.
As for Kotlin
, I’ll use Android
project as an example as I’m mostly Android
developer.
You can find JaCoCo configuration
in one of my Github projects (ci-matters, written in Java).
apply plugin: 'jacoco'
ext {
coverageSourceDirs = 'src/test/java'
}
jacoco {
toolVersion = "0.7.5.201505241946"
reportsDir = file("$buildDir/reports")
}
task jacocoTestReport(type:JacocoReport, dependsOn: "testDebugUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports for Debug build"
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir: "$buildDir/intermediates/classes/debug",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewBinder*.*',
'**/*$InjectAdapter*.*',
'**/*Injector*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'**/*Activity*.*',
'**/CiMattersApplication*.*',
'android/**/*.*']
)
if (project.hasProperty("teamcity")) {
println '##teamcity[jacocoReport dataPath=\'app/build/jacoco/testDebugUnitTest.exec\' includes=\'com.vgaidarji.cimatters.*\' excludes=\'com.vgaidarji.cimatters.test.* **/*R*.* **/*Injector*.* **/*Activity*.* .*R .*CiMattersApplication .*BuildConfig .*Activity .*Test \']'
}
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("$buildDir/jacoco/testDebugUnitTest.exec")
// Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
// We iterate through the compiled .class tree and rename $$ to $.
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
This is more or less standard way of configuring JaCoCo
on Android
project and almost everyone does it this way.
Let’s see what is different for Java + Kotlin
project.
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.9"
reportsDir = file("$buildDir/reports")
}
task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports for Debug build"
reports {
xml.enabled = true
html.enabled = true
}
// what to exclude from coverage report
// UI, "noise", generated classes, platform classes, etc.
def excludes = [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
'**/*Fragment.*',
'**/*Activity.*'
]
// generated classes
classDirectories = fileTree(
dir: "$buildDir/intermediates/classes/debug",
excludes: excludes
) + fileTree(
dir: "$buildDir/tmp/kotlin-classes/debug",
excludes: excludes
)
// sources
sourceDirectories = files([
android.sourceSets.main.java.srcDirs,
"src/main/kotlin"
])
executionData = files("$buildDir/jacoco/testDebugUnitTest.exec")
}
The “trick” here is to specify where are located Java/Kotlin
generated classes and sources.
Here we combine 2 folders:
fileTree(
// Java generated classes on Android project (debug build)
dir: "$buildDir/intermediates/classes/debug",
excludes: excludes
) + fileTree(
// Kotlin generated classes on Android project (debug build)
dir: "$buildDir/tmp/kotlin-classes/debug",
excludes: excludes
)
Here we specify where are our source Java/Kotlin
classes located:
sourceDirectories = files([
android.sourceSets.main.java.srcDirs,
"src/main/kotlin"
])
That’s it! As result, we will have combined JaCoCo
report generated
once we run our tests and gather code coverage using ./gradlew testDebugUnitTest jacocoTestReport
command.
It should work for any type of Java/Kotlin
project, not only Android
.
Worth mentioning, that I didn’t find any issues so far with JaCoCo
plugin on Kotlin/Java
project.
Maybe there are none.
But I wouldn’t be surprised if one day someone builds KoCoCo
(Kotlin Code Coverage tool).