Gradle, Android, Jacoco and JUnit5

2.4k views Asked by At

Now with the release of JUnit 5.0 I'm struggling to get code coverage data using jacoco.

My project is multi-module and my problem is that there is no exec file created for each module. Only one in the root project (which seems almost empty). I've not been able to find an up-to-date guide for this.

I've tried different approaches described here: Gradle Jacoco and JUnit5 without any success.

Does anyone have a working setup in a multi-module (non-Android) Gradle project with JUnit5 and Jacoco?

Or a Gradle Android project with JUnit5 and Jacoco? Any advice is greatly appreciated. Even ugly hacks are welcome until there is some official documentation available.

Relevant part of my code (where running JUnit5 works both in commandline and from the IDE at least):

<Root> build.gradle:
buildscript {
    dependencies {
        ...
        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0'
        classpath "de.mannodermaus.gradle.plugins:android-junit5:1.0.0"
    }
}

...

apply plugin: 'org.junit.platform.gradle.plugin'

ext.junitVintageVersion = '4.12.0'
ext.junitPlatformVersion = '1.0.0'
ext.junitJupiterVersion = '5.0.0'

subprojects {

    apply plugin: "jacoco"
    jacoco {
        toolVersion = "0.7.6.201602180812"
    }

    ...

  junitPlatform {
        filters {
            engines {
                include 'junit-jupiter', 'junit-vintage'
            }
            tags {
                exclude 'slow'
            }
            includeClassNamePatterns '.*Test', '.*Tests'
        }
    }
}
...
dependencies {
    // org.junit.platform.commons.util.PreconditionViolationException:
    // Cannot create Launcher without at least one TestEngine; consider adding an engine implementation JAR to the classpath
    testCompile("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
2

There are 2 answers

2
Godin On

I've tried different approaches described here: Gradle Jacoco and JUnit5 without any success.

For non-Android projects these approaches look good.

Does anyone have a working setup in a multi-module (non-Android) Gradle project with JUnit5 and Jacoco?

But anyway below is a complete working example of non-Android project.

Or a Gradle Android project with JUnit5 and Jacoco?

Be aware that Android projects are completely different beasts compared to non-Android, including but not limited to the configuration/execution of tests and computation of coverage.

In particular - by default JaCoCo library (http://www.jacoco.org/jacoco/index.html) changes classes for recording of coverage "on-the-fly" during execution of application using Java agent. And this mode is exactly what default JaCoCo Gradle Plugin (https://docs.gradle.org/4.1/userguide/jacoco_plugin.html) provides.

Java agents can't be used on Android, and so for Android change of classes happens "off-line" (http://www.jacoco.org/jacoco/trunk/doc/offline.html) during build by Android Gradle Plugin (https://developer.android.com/studio/releases/gradle-plugin.html).

Also seems that org.junit.platform:junit-platform-gradle-plugin is not applicable for Android projects - there is https://github.com/aurae/android-junit5 and for the time being open discussion about coverage at https://github.com/aurae/android-junit5/issues/4

Not being a daily developer of Android projects, I can't provide more information than this.


settings.gradle:

include 'a'
include 'b'

build.gradle:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0'
  }
}

ext.junitJupiterVersion = '5.0.0'

subprojects {
  repositories {
    mavenCentral()
  }

  apply plugin: 'java'

  apply plugin: 'org.junit.platform.gradle.plugin'

  apply plugin: 'jacoco'
  jacoco {
    toolVersion = '0.7.9'
    applyTo junitPlatformTest
  }
  junitPlatformTest {
    jacoco {
      destinationFile = file("$buildDir/jacoco/test.exec")
    }
  }

  dependencies {
    testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
    testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
  }
}

task wrapper(type: Wrapper) {
  gradleVersion = '4.1'
}

a/src/main/java/A.java:

public class A {
}

a/src/test/java/ATest.java:

public class ATest {
  @org.junit.jupiter.api.Test
  public void test() {
    new A();
  }
}

b/src/main/java/B.java:

public class B {
}

b/src/test/java/BTest.java:

public class BTest {
  @org.junit.jupiter.api.Test
  public void test() {
    new B();
  }
}

After execution of gradle test jacocoTestReport files a/build/jacoco/test.exec and b/build/jacoco/test.exec are generated as well reports shown below.

a/build/reports/jacoco/test/html/index.html:

a/build/reports/jacoco/test/html/index.html

b/build/reports/jacoco/test/html/index.html:

b/build/reports/jacoco/test/html/index.html

0
Alix On

Thanks to the guidance of Godins example I was able to get some* code coverage working (verify by opening the generated .exec file in your IDE and check that coverage is displayed):

* Some meaning coverage from JUnit4 tests running under JUnit5

gradlew junitPlatformTestDebug

--> $buildDir/jacoco/junitPlatformTestDebug.exec

settings.gradle:

include 'a'
include 'b'

build.gradle (parent):

buildscript {
  dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
    classpath 'de.mannodermaus.gradle.plugins:android-junit5:1.0.0'
  }
}
    
subprojects {

  apply plugin: 'jacoco'
  jacoco {
    toolVersion = '0.7.9'
  }

}

build.gradle (a - main app):

apply plugin: 'com.android.application'
apply plugin: 'de.mannodermaus.android-junit5'

android {
    testOptions {
        unitTests.all {
            jacoco {
                includeNoLocationClasses = true
            }
        }
    }
    junitPlatform {
        jupiterVersion '5.0.0'
        vintageVersion '4.12.0'
    }
}
dependencies {
    testCompile junit5()
}
project.afterEvaluate {
    // Workaround: https://stackoverflow.com/questions/39362955/gradle-jacoco-and-junit5/39386661#39386661
    apply plugin: "jacoco"
    jacoco {
        applyTo junitPlatformTestDebug
    }
}
 

build.gradle (b - Android library module):

apply plugin: 'com.android.library'
apply plugin: 'de.mannodermaus.android-junit5'

android {
    testOptions {
        unitTests.all {
            jacoco {
                includeNoLocationClasses = true
            }
        }
    
    junitPlatform {
        jupiterVersion '5.0.0'
        vintageVersion '4.12.0'
    }
}
dependencies {
    testCompile junit5()
}
project.afterEvaluate {
    // Workaround: https://stackoverflow.com/questions/39362955/gradle-jacoco-and-junit5/39386661#39386661
    apply plugin: "jacoco"
    jacoco {
        applyTo junitPlatformTestDebug
    }
}