Kotlin Multiplatform unlocks new possibilities for developing cross-platform applications. However, this innovative approach does not come without its complexities. Especially when delving into its specific Gradle configuration. One of the challenges is establishing a reliable, automated test coverage report – an integral part of maintaining code quality and integrity. In this mini-series, we’ll learn how to leverage JaCoCo Gradle plugin to generate code coverage reports. Additionally, we’ll leverage GitHub Actions to generate a coverage badge independently from any third-party platform. This approach simplifies the process by keeping everything within the GitHub ecosystem, providing a seamless workflow for your multi platform projects.

Test coverage is a metric used to measure the amount of code that is executed when automated tests run. It helps identify untested parts of your codebase, ensuring that the critical paths and functionalities are validated through tests. This is especially crucial for Kotlin Multiplatform projects, whose code base spans multiple platforms (such as JVM, web, iOS, Android etc.). Given the diverse range of platforms and the inherent complexity in coding for them, achieving high test coverage ensures that the shared codebase works as expected. This not only boosts the confidence in the code’s reliability and quality but also streamlines the development process by catching bugs early, reducing the likelihood of platform-specific issues, and facilitating easier maintenance and updates of your application.

Table of Contents

TL;DR

If you are just looking for a quick solution that enables test coverage in your Kotlin Multiplatform project, explore my complete template.

Why to Use JaCoCo in Kotlin Multiplatform Projects?

The JaCoCo Gradle plugin is a popular tool that integrates seamlessly with Gradle projects, including those written in Kotlin, to measure code coverage. It provides detailed reports on how much code is being executed in tests, highlighting what’s been covered and what’s been missed. Kotlin Multiplatform projects include shared logic as well as platform-specific intricacies. JaCoCo offers invaluable insights that help developers write more comprehensive tests and maintain high code quality.

The Starting Point

Here is a skeleton of build.gradle.kts of an KMP project that targets JVM and JavaScript platforms.

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    id("module.publication")
    id("org.jetbrains.kotlin.plugin.serialization")
}

kotlin {
    jvm {
        compilations.all {
            kotlinOptions {
                jvmTarget = "17"
            }
        }
    }

    js(IR) {
        moduleName = "zoomsdk"
        browser {
            webpackTask {
                output.libraryTarget = "umd"
            }
        }
        nodejs {}
        binaries.executable()
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
               // Platform independent libraries
            }
        }
        val commonTest by getting {
            dependencies {
               // Platform independent test libraries
            }
        }
        val jvmMain by getting {
            dependencies {
                // JVM specific libraries
            }
        }

        val jvmTest by getting {
            dependencies {
               // JVM specific test libraries, such as JUnit
            }
            tasks.withType<Test> {
                useJUnitPlatform()
                testLogging {
                    events("passed", "skipped", "failed")
                }
            }
        }
        val jsMain by getting {
            dependencies {
              // JS specific dependencies
            }
        }
        val jsTest by getting {
            dependencies {
                // JS specific test dependencies
            }
        }
    }
}

Curious about why I specifically chose this setup and where it all started? Feel free to check out the original project on GitHub. I’ll save the shameless plug for another day 😉

Step 1 – Add and Configure JaCoCo Plugin

First of all, add jacoco in the plugin section.

plugins {
  jacoco
  // your other plugins
}

Next, make sure to use the latest version (0.8.12 as of time of this writing) and optionally override the directory where the test coverage reports will be generated. The default location is build/reports/jacoco. In the example below I could’ve skipped declaring it, but let’s just leave it in for illustration purposes.

jacoco {
    toolVersion = "0.8.12"
    reportsDirectory = layout.buildDirectory.dir("reports/jacoco")
}

Step 2 – Register A Report Task

To ensure that test coverage reports are automatically generated each time tests are executed during the build process, we need to configure Gradle accordingly. This involves defining a new task within the build script. We will name this task jacocoTestReport, aligning with the convention used by the JaCoCo plugin for Gradle, which facilitates the creation of these reports.

tasks.register("jacocoTestReport", JacocoReport::class) {
  dependsOn(tasks.withType(Test::class))
}

To enable automatic generation of the coverage report by Gradle, we must establish a dependency relationship. This is done by declaring that our custom task, jacocoTestReport, relies on the successful completion of the test task. Furthermore, we must ensure that Gradle generates the report immediately after the JVM tests conclude. To accomplish this, we integrate a finalizedBy configuration block within our test task setup.

val jvmTest by getting {
  dependencies {
    // JVM specific test libraries, such as JUnit
  }
  tasks.withType<Test> {
    finalizedBy(tasks.withType(JacocoReport::class))
    // The rest of the usual configuration
  }
}

Step 3 – Define The Monitored Code Base

JaCoCo is primarily designed for Java, which makes it an excellent choice for projects implemented in Kotlin, given Kotlin’s seamless compatibility with the JVM. This ensures comprehensive code coverage tracking for both Java and Kotlin codebases. However, for JavaScript and web development projects, we might consider other tools more tailored to the specific challenges and needs of the web ecosystem.

Therefore, let’s instruct JaCoCo to monitor our common code base, along with the code specific to the JVM.

tasks.register("jacocoTestReport", JacocoReport::class) {
  dependsOn(tasks.withType(Test::class))
  val coverageSourceDirs = arrayOf(
    "src/commonMain",
    "src/jvmMain"
  )
}

Step 4 – Help JaCoCo Understand Your Code Base

In the JaCoCo Gradle plugin, sourceDirectories and classDirectories have an essential role in determining what code is considered when evaluating test coverage.

The sourceDirectories property specifies the directories containing the source code for the project. The JaCoCo plugin uses these directories to locate the source files that your tests are targeting. In the previous step we’ve declared a custom variable coverageSourceDirs that points to our common and JVM source code.

The classDirectories property specifies the directories containing the compiled class files. These class files are the actual bytecode that runs when your program is executed. By monitoring these during testing, JaCoCo can accurately determine which pieces of code are executed during testing and which aren’t.

Here is the resulting configuration.


val buildDir = layout.buildDirectory

// Include all compiled classes.
val classFiles = buildDir.dir("classes/kotlin/jvm").get().asFile
                .walkBottomUp()
                .toSet()

// This helps with test coverage accuracy.
classDirectories.setFrom(classFiles)
sourceDirectories.setFrom(files(coverageSourceDirs))

// The resulting test report in binary format.
// It serves as the basis for human-readable reports.
buildDir.files("jacoco/jvmTest.exec").let {
  executionData.setFrom(it)
}

Step 5 – Define The Report Format

JaCoCo is capable of producing test coverage reports in HTML, XML, or CSV formats. Depending on your specific needs, you can use any one of these formats, or even all of them.

reports {
  xml.required = true
  html.required = true
}

The Complete Template

To simplify the integration of test coverage reporting into Kotlin Multiplatform projects, I’ve developed a template that encapsulates all the configurations discussed in this post. This includes automated report generation and setup for multi-platform testing scenarios. You’re invited to explore and use this template to streamline your project setup. Find it on my GitHub, and enhance your development workflow.

Summary

Kotlin Multiplatform significantly enhances developer productivity and reduces the possibility of human error. In this post, we’ve delved into advancing our project by integrating test coverage reports. In the upcoming second part of this mini-series, we will delve into incorporating test coverage reports into our CI/CD processes using GitHub Actions. By the conclusion of this tutorial, you’ll achieve a notable milestone: Showcasing a custom test coverage badge in your README file, achieved without requiring any third-party services. Stay tuned for more insights and thank you for reading.


Tomas Zezula

Hello! I'm a technology enthusiast with a knack for solving problems and a passion for making complex concepts accessible. My journey spans across software development, project management, and technical writing. I specialise in transforming rough sketches of ideas to fully launched products, all the while breaking down complex processes into understandable language. I believe a well-designed software development process is key to driving business growth. My focus as a leader and technical writer aims to bridge the tech-business divide, ensuring that intricate concepts are available and understandable to all. As a consultant, I'm eager to bring my versatile skills and extensive experience to help businesses navigate their software integration needs. Whether you're seeking bespoke software solutions, well-coordinated product launches, or easily digestible tech content, I'm here to make it happen. Ready to turn your vision into reality? Let's connect and explore the possibilities together.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *