Blog Post

Code Coverage for Android Development Using Kotlin, JaCoCo, GitHub Actions, and Codecov

March 3, 2021 Wisdom Nwokocha

You know testing your code is important—but how effective are your tests? How many lines of code does it address, and does it touch enough of the subroutines? Code coverage is the measurement of how much of your source code a test covers, and understanding it can go a long way toward being confident in your test results.

How Do I Understand My Code Coverage?

Reporting tools like Codecov can take your code coverage report and turn it into meaningful data, like how pull requests will affect code coverage. Codecov works in tandem with your continuous integration (CI) system to analyze every commit, so you get insight into how your tests are performing right in your own workflow.

In short, knowing your percentage of code coverage, and what it means, can help you write better tests for your apps, which leads to building better apps. In this article, you’ll learn how to integrate JaCoCo, a free code coverage library in Java, into your Android project, then generate a report for analysis with Codecov. If you’d like, you can check out this tutorial’s GitHub repo.

Implementing Code Coverage in Android

First, let’s walk through how to implement code coverage in your Android project. You’ll write a unit test, add JaCoCo to your project, and automate the process using GitHub Actions.

To begin, make sure you’re familiar with the following prerequisites:

  • GitHub Actions
  • Kotlin
  • Android Studio 4.2

Then it’s time to write some unit tests for your project so JaCoCo can generate a report for you:

  @Test
    fun counttext() {
        val yu = TextMethods.counttext("tuuuuu", "")
        // assertTrue("text contains two characters", yu == 2)
        //  assertNotNull("text is not null", yu)
        assertThat(yu).isGreaterThan(4)

    }

    @Test
    fun capitalizeText() {
        val yu = TextMethods.capitalizeText("tuuuuu", "")
        assertThat(yu).contains("TUUUUU")
    }

Now, add JaCoCo to your project.

Gradle project level

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.4.21"
    ext.jacocoVersion = '0.8.4'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jacoco:org.jacoco.core:$jacocoVersion"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

Gradle module level

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'jacoco'
}

task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {

    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"

    sourceDirectories.setFrom(files([mainSrc]))
    classDirectories.setFrom(files([debugTree]))
    executionData.setFrom(fileTree(dir: "$buildDir", includes: [
            "jacoco/testDebugUnitTest.exec",
            "outputs/code-coverage/connected/*coverage.ec"
    ]))
}

testOptions {
        unitTests.all {
            jacoco {
                includeNoLocationClasses = true
            }
        }
        unitTests.returnDefaultValues = true
    }


    buildTypes {
        debug {
            testCoverageEnabled true
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

Sync your project in Android Studio to include the JaCoCo Gradle task and classes that will enable you to generate code coverage for your app.

After enabling JaCoCo in your project, generate your first code coverage in HTML format. The following Gradle command will generate a test coverage report for your project:

.gradlew connectedCheck

This will generate a report for your unit test alone:

.gradlew testDebugUnitTest

And this will generate the report for an instrumented test:

.gradlew connectedDebugAndroidTest

You can check the code coverage report of your instrumented and unit tests locally. For your instrumented test, locate your Android studio projects folder in your system. Then navigate to your project folder: _(CodeCoverageExample) > app > build > reports > androidTests > connected > flavors > debugAndroidTest > Index.html.

You’ll arrive at this screen after clicking Index.html to view the report on your web browser:

Instrumented test report

For the unit test, navigate to …reports > tests > testDebugUnitTest > Index.html. After clicking on index.html, you’ll see the following screen:

Unit test report

To see the test result, navigate to _com.dev.codecoverageexample.util > TestMethodsTest. To send a code coverage report to Codecov, first navigate to coverage > debug > index.html to choose a supported file format.

Finally, let’s push your project to GitHub.

Sample code coverage GitHub project

Before setting up GitHub Actions as the CI for your project, there are a few terms you should know to help you understand CI/CD using GitHub Actions:

  • Job: A job in GitHub Actions contains a sequence of tasks called steps.
  • Steps: Steps can run commands.
  • Workflow: A workflow is a configurable automated process made up of one or more jobs.

To add GitHub Actions to your workflow, click the Actions tab at the top of your repository window.

Click Actions at the top of the screen

Select a workflow template from the list that matches your programming language or tool.

List of Github Actions workflow templates

Click Set up this workflow to add Github Actions to your project.

Set up this workflow

Click Start commit.

The green Start commit button

Then name your file and click Commit new file to commit to the main branch or create a new branch.

The Commit new file window

You can decide to edit the GitHub Actions workflow before committing your new file, but for this tutorial, commit the default workflow to the master branch and modify it later.

Next, let’s integrate the Codecov plugin into your project in GitHub so you can push a test report to Codecov. First, navigate on your browser to https://github.com/apps/codecov.

Click Install > Select your GitHub profile > Only select repositories. Select your desired repositories, then click Install.

Installing Codecov in GitHub

Note that you should choose Only select repositories because you want Codecov to be installed only on one repo, rather than all your repos on Github.

With your selections made, you will see a Welcome to Codecov message. To access your Codecov dashboard, click the Login with GitHub icon.

Welcome to Codecov

Click Add repository to see a list of your repos. Select the repo whose code you want covered. Note that when using GitHub Actions with Codecov, you’ll need a unique upload token only if your repo is private. The token is required to identify which project the coverage belongs to.

A unique upload token

Before pushing your code coverage report to Codecov, take note of the report formats Codecov supports:

  • .xml (Cobertura XML, JaCoCo XML, etc.)
  • .json (Erlang JSON, Elm JSON, etc.)
  • .txt (LCOV, Gcov, Golang)

For this tutorial, you’ll need the JaCoCo XML report format.

To automate the process of sending your code coverage report to Codecov, follow these steps:

  1. Add a YAML file in the root of your project. The name of the file and extension should be codecov.yml.

The codecov .yml file

  1. Add the following workflow in your GitHub action .yml file. Note that where you see CodeCoverage_Example, replace it with your project name.
{
    "fixes": [
        "/home/runner/work/CodeCoverage_Example/CodeCoverage_Example>/::"
    ],
    "codecov": {
        "disable_default_path_fixes": true
    }
}
name: CI

# Controls when the action will run. 
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: macos-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

       # Execute unit tests
      - name: Unit Test with Android Emulator Runner
        uses: ReactiveCircus/android-emulator-runner@v2.14.3
        with: 
            api-level: 29
            script: ./gradlew connectedCheck 

       # run: ./gradlew testDebugUnitTest

      - name: generate report
        uses: actions/upload-artifact@v2
        with:
          name: report 
          path: app/build/reports/coverage/debug


      - name: Download Test Reports Folder
        uses: actions/download-artifact@v2
        with:
          name: report
          path: app/build/reports/coverage/debug

      - name: Upload Test Report
        run:  bash <(curl -s https://codecov.io/bash) -f "app/build/reports/coverage/debug/report.xml"

You can now commit your changes and wait for your build to finish. If the build is successful, you’ll see a green checkmark beside the commit name, and red if it’s not successful.

All workflow runs

Click Update codecoverage.yml to see the workflow.

GitHub Actions build

Click build to see the logs.

Build succeeded

Click Upload Test Report to see the code coverage upload to Codecov logs.

A code coverage workflow

On the bottom of the screen, click the URL after View report at: to be able to see your code coverage on the Codecov platform.

Code coverage logs

A report on the Codecov platform

Conclusion

Code coverage is the measurement of how much of your source code your tests actually touch. In Android development, you can generate test coverage reports locally using JaCoCo, and then remotely store them using Codecov.

You can automate the process of generating and sending your code coverage to any code coverage storage platform. Codecov makes it easy to send your code coverage, supports many programming languages and frameworks, supports lots of code coverage report formats, and can be integrated into many CI/CD platforms, like GitHub Actions and Bitrise.