Playing with Gradle[3 and some few]: Code Coverage on Android with Jacoco

Author’s highlight

Mathias thinks you, as Android developer, can be interested by the following article : Rx Android everywhere… why, but why ?

Playing with Gradle series:

Chapter 1: Basics

Chapter 2: Android Tasks Basics

Chapter 3: Flavors, BuildTypes and Variants

Chapter 3 1/4: Flavors And Manifest

Chapter 3 and some few: Understanding the life cycle.

Chapter 3 and some few more: Code Coverage on Android with Jacoco

Chapter 4: Getting real

(Download the ebook: https://android2ee.com/Tutoriaux/Playing-with-Gradle.html for free and without any registration or what ever, it’s a gift to the community).

Setting code coverage on Android with Jacoco

You also want to have your code coverage on Android, but it doesn’t work, there is a lot a tutorial explaining how to do, github project, gradle plugin… but all this resources (most of them are deprecated but you don’t know it) are just messing our mind and we finish by not being sure if Android build system is able or not to generate code coverage.

But it should be simple, and in fact, it is. So instead of giving you the receipt, I prefer to give you the understanding. Because we want and need that:

And when you have understood it’s easy to set even with a bunch of flavor dimensions.

So the goal is to have a final report on your tests coverage for all your tests. Let’s do that.

Enabling Jacoco on your project

By default, you have Jacoco in your Android build system, you just have to enable it and you’ll have your first report each time you run tests.

Do to that, you have to enable it in 2 different places in your build.gradle.

Enable code coverage for instrumented tests

You activate it for the build you want (debug or release):

buildTypes {
 release {
 //your debug part
 //add tests coverage using Jacoco
 
testCoverageEnabled false
 
}
 debug {
 //your debug part

//add tests coverage using Jacoco
 
testCoverageEnabled true
 
}
 }

Doing that enable your tests reports for all you instrumented test for your Debug build type. As I only need it for Debug, I just set it on the Debug build type.

Enable tests coverage for UnitTests

To enable your code coverage for unit tests add this gradle hook (in your build.gradle or in a separate file called by your build.gradle)

//Enable the coverage report for unit test
android.testOptions {
    unitTests.all {
        jacoco {
            includeNoLocationClasses = true
        }
    }
}

You did it, tests coverage is enable on the project.

What happened

Doing so, you have created in your project several gradle tasks that are here to generate your .ec files (raw data of coverage report) for your instrumented tests.

You can find them in the category other.

And the same tasks for your unit tests reports (category verification)

Now, each time you run your tests, those tasks are executed and jacoco will generate the raw file of the coverage report for this set of tests in a specific format. Exec for unit tests and ec for instrumented tests, you can find them in your build folder.

Defining and creating your report

Then let’s create a specific gradle task, that will generate the report, this way, you can call it from AndroidStudio or directly with the gradle console.

The algorithm of this task is:

· Run your tests and generate your exec and ec files

· Define which files you want to exclude from the analysis (auto generated code, code from library, some of your classes that are tests classes, or pojo)

· Define where is the code to analyse; you need to give the location of the .class files (in build/intermediates) and the package they belong in your source code (for exemple src/main/java)

· Define which in which files are your raw coverage data generated by Jacoco (the .ec and .exec files)

And that’s all. Crazy, I hunt this information in the 2 first google pages results of “jacoco android gradle” or such a request.

Now we know what to do, let’s do it:

apply plugin: 'jacoco'
//Your task is task of type Jacoco
//and you have to run all your tests task and you create***CodeCoverage one
//those tasks are generated when you enable Jacoco (first chapter)
task jacocoTestReport(type: JacocoReport, dependsOn: [
//if you have already run those tasks in your build, just comment them
'testMycmsMockDebugUnitTest',
        'testMycmsProdDebugUnitTest',
        'createMycmsMockDebugCoverageReport',
        'createMycmsProdDebugCoverageReport',
]) {
//Define which type of report you want to generate
    reports {
        xml.enabled = true
        html.enabled = true
    }
    //define which classes to exclude
    def fileFilter = [
            '**/R.class',
            '**/R$*.class',
            '**/BuildConfig.*',
            '**/Manifest*.*',
            '**/*$ViewInjector*.*',
            '**/*$ViewBinder*.*',
            '**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
            '**/*Module.*', // Modules for Dagger.
            '**/*Dagger*.*', // Dagger auto-generated code.
            '**/*MembersInjector*.*', // Dagger auto-generated code.
            '**/*_Provide*Factory*.*',
            '**/*_Factory.*', //Dagger auto-generated code
            '**/*$*$*.*', // Anonymous classes generated by kotlin
            //add libraries
            'android/**/*.*',
            'com/**/*.*',
            'uk/**/*.*',
            'io/**/*.*',
            //remove what we don't test
            'androidTest/**/*.*',
            'test/**/*.*',
            '**/injector/**/*.*',
            '**/model/**/*.*',
            '**/mock/**/*.*',
            '**/event/**/*.*',
            '**/**_ViewBinding**',
            '**/*EventType.*',
            '**/**Mocked'
    ]
    //Define your source and your classes: we want to test the production code
    def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/mycmsProd/debug", excludes: fileFilter)
    def mainSrc = files(["src/main/java","src/mycms/java"])
    //Explain to Jacoco where is your source code
    sourceDirectories = files([mainSrc])
//Explain to Jacoco where are you .class file
    classDirectories = files([debugTree])
    //As you want to gather all your tests reports, add the ec and exec you want to be took into
    //account when generating the report
    executionData = fileTree(dir: "$buildDir", includes: [
            "jacoco/testMycmsMockDebugUnitTest.exec",
            "jacoco/testMycmsProdDebugUnitTest.exec",
            "outputs/code-coverage/connected/flavors/**/*coverage.ec"
    ])
}

References

https://medium.com/@rafael_toledo/setting-up-an-unified-coverage-report-in-android-with-jacoco-robolectric-and-espresso-ffe239aaf3fa

and

https://medium.com/contentsquare-engineering-blog/make-or-break-with-gradle-dac2e858868d