You have a Kotlin Android app and you want SonarQube code coverage reports? Peter-John Welcome found a way. Read his guide.
Guest post by Peter-John Welcome, the original post appeared on AndroidPub.
Peter-John is a mobile developer at Dynamic Visual Technologies, developing a banking app for one of South Africa’s biggest banks. He works on both Android and iOS platforms and enjoys using new technologies and APIs. Big Kotlin and Swift fan. In his spare time, he's into photography, blogging about technology and co-organising developer meetups at GDG Johannesburg.
I’ve recently been working on an Android app that is 100% Kotlin with a full development CI pipeline. Having a good app means that the code is testable, which led me to use SonarQube for static code analysis and code coverage.
Since there is no official Kotlin plugin for SonarQube yet, I looked at a third party plugin on GitHub named Sonar-Kotlin. This plugin is awesome as it has another library called detekt integrated into it. Detekt is what does the static code analysis. What about the code coverage you ask? Well, the Sonar-Kotlin plugin has the ability to give us code coverage reports, but there is some set up that needs to be done.
This is where my journey with SonarQube code coverage began, with my Android app written in Kotlin.
This post assumes you have SonarQube setup and some kind of CI.
Getting Started
Getting the Sonar-Kotlin plugin installed is very simple. I have a VM running on Google Compute Engine that has SonarQube installed in a Docker container. Then pretty much followed the steps on the plugin's Github page and it was installed.
- git clone https://github.com/arturbosch/sonar-kotlin
- cd sonar-kotlin
- ./gradlew build
- cp build/libs/sonar-kotlin-[enter_version].jar $SONAR_HOME/extensions/plugins
- $SONAR_HOME/bin/[your_os]/sonar.sh restart
The CI I’m using for the project is Bitrise. This is a pretty awesome CI for its workflow editor and all its third-party integrations. One of those third party tools Bitrise provides is Sonarqube scanner.
Editor's note: the SonarQube Scanner step was written by @koral of DroidsOnRoids as a community contribution. Thanks for it. 🙂
Within Bitrise we will add our scanner properties so when the SonarQube scanner build step is invoked, those properties will be executed accordingly.
sonar.sources=.
sonar.login=$SonarToken
sonar.projectKey=$BITRISE_APP_TITLE
sonar.host.url=$SonarUrl
sonar.report.export.path=sonar-report.json
detekt.sonar.kotlin.config.path=default-detekt-config.yml
sonar.java.binaries=target/classes
sonar.sources=app/src/main/java
sonar.tests=app/src/test/java
sonar.java.coveragePlugin=jacoco
sonar.jacoco.reportPaths=app/build/jacoco/testDebugUnitTest.exec
So for us to get a sonar code coverage report for Java code, we need to have a gradle task that will generate a Jacoco report. This is the same for Kotlin.
I found this article to help you get started with Jacoco.
Once we have that set up within our workflow, we need to run the following gradle command before the SonarQube scanner task runs:
./gradlew clean jacocoTestReport
The scanner needs a Jacoco report to show code coverage in SonarQube and the command above provides that for us.
If we run a build now, we will see the following SonarQube report:
As seen above, we do not have code coverage, even though I said at the beginning that the plugin supports code coverage for kotlin code.
This is due to the Kotlin class files not being generated in the same location as Java class files. For us to get around this, we need to add the location of the Kotlin class files to the sonar.java.binaries property.
sonar.java.binaries=target/classes,app/build/tmp/kotlin-classes
This location can differ depending on your project. We need to keep the “target/classes” in order for the Java class files to be picked up as well.
We then run our CI build again and we get code coverage for our Kotlin code.
A lot of experimenting was done to get to this point. There is not much help out there in getting this work, which is the reason for me sharing my experience. I hope this helps everyone out there who is trying to solve the same problem I had.
Happy SonarQube code coverage.