37

I'm new to Gradle and Android testing but I've already converted my Android project to build with Gradle.

Now I'm trying to perform test coverage of an Android project with Gradle's JaCoCo plugin.

I've added the following to my build.gradle file:

apply plugin: 'jacoco'

And when I run "gradle jacocoTestReport" the following error:

Task 'jacocoTestReport' not found in root project '<project name>'.

From the documentation I'm supposed to also apply plugin 'java' but it conflicts with plugin 'android'.

Is there a way around this?

Thanks in advance.

Henrique Rocha
  • 1,737
  • 1
  • 19
  • 29
  • Test coverage is not supported yet by the android gradle plugin. I am looking for a way to achieve too, but it looks hopeless now as the gradle plugin for android doesn't tell android to generate any coverage. – Snicolas Oct 04 '13 at 18:01
  • until the java plugin and android plugin are compatible together, you could use ant.java to execute the tests and also produce coverage report. basically do what you would do in ANT. – skipy Nov 26 '13 at 11:19
  • 2
    @skipy: Do you have an example of how to do this in ant? I haven't been able to find an example of configuring `jacocoagent` and retrieving the report from the emulator. – unholysampler Dec 23 '13 at 01:22

4 Answers4

25

Here is how I'm using Jacoco:

buildscript {
  repositories {
    mavenLocal()
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:0.12.+'
    classpath 'org.robolectric:robolectric-gradle-plugin:0.11.+'
  }
}

apply plugin: 'com.android.application'
apply plugin: 'robolectric'
apply plugin: 'jacoco'

android {
  compileSdkVersion 20
  buildToolsVersion "20.0.0"

  defaultConfig {
    applicationId "YOUR_PACKAGE_NAME"
    minSdkVersion 10
    targetSdkVersion 20
    testHandleProfiling true
    testFunctionalTest true
  }
  buildTypes {
    debug {
      testCoverageEnabled false
    }
    release {
      runProguard false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
    }
  }
  jacoco {
    version "0.7.1.201405082137"
  }
  packagingOptions {
    exclude 'META-INF/DEPENDENCIES.txt'
    exclude 'META-INF/LICENSE.txt'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/DEPENDENCIES'
    exclude 'META-INF/notice.txt'
    exclude 'META-INF/license.txt'
    exclude 'META-INF/dependencies.txt'
    exclude 'META-INF/LGPL2.1'
    exclude 'META-INF/services/javax.annotation.processing.Processor'
    exclude 'LICENSE.txt'
  }
}

robolectric {
  include '**/*Test.class'
  exclude '**/espresso/**/*.class'

  maxHeapSize "2048m"
}

jacoco {
  toolVersion "0.7.1.201405082137"
}

// Define coverage source.
// If you have rs/aidl etc... add them here.
def coverageSourceDirs = [
    'src/main/java',
]

task jacocoTestReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
  group = "Reporting"
  description = "Generate Jacoco coverage reports after running tests."
  reports {
    xml.enabled = true
    html.enabled = true
  }
  classDirectories = fileTree(
      dir: './build/intermediates/classes/debug',
      excludes: ['**/R*.class',
                 '**/*$InjectAdapter.class',
                 '**/*$ModuleAdapter.class',
                 '**/*$ViewInjector*.class'
      ])
  sourceDirectories = files(coverageSourceDirs)
  executionData = files("$buildDir/jacoco/testDebug.exec")
  // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
  // We iterate through the compiled .class tree and rename $$ to $.
  doFirst {
    new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
      if (file.name.contains('$$')) {
        file.renameTo(file.path.replace('$$', '$'))
      }
    }
  }
}


dependencies {
  androidTestCompile('junit:junit:4.11') {
    exclude module: 'hamcrest-core'
  }
  androidTestCompile('org.robolectric:robolectric:2.3') {
    exclude module: 'classworlds'
    exclude module: 'maven-artifact'
    exclude module: 'maven-artifact-manager'
    exclude module: 'maven-error-diagnostics'
    exclude module: 'maven-model'
    exclude module: 'maven-plugin-registry'
    exclude module: 'maven-profile'
    exclude module: 'maven-project'
    exclude module: 'maven-settings'
    exclude module: 'nekohtml'
    exclude module: 'plexus-container-default'
    exclude module: 'plexus-interpolation'
    exclude module: 'plexus-utils'
    exclude module: 'wagon-file'
    exclude module: 'wagon-http-lightweight'
    exclude module: 'wagon-http-shared'
    exclude module: 'wagon-provider-api'
    exclude group: 'com.android.support', module: 'support-v4'
  }
}

The above code also contains a workaround for https://code.google.com/p/android/issues/detail?id=69174.

More details: http://chrisjenx.com/gradle-robolectric-jacoco-dagger/

ByteWelder
  • 5,464
  • 1
  • 38
  • 45
Hieu Rocker
  • 1,070
  • 12
  • 19
  • How did you integrate script above with Android plugin? Can you give me the link to see your gradle files? – Borys Jul 16 '14 at 15:15
  • @Borys assume you already have an exist `build.gradle` with robolectric integrated. You just need to put `apply plugin: 'jacoco'` at `apply plugin` section, then put the rest of the above code to the end of your `build.gradle`. You can then run `./gradlew testDebug jacocoTestReport`. That's it. – Hieu Rocker Jul 17 '14 at 05:14
  • Hm.... I got next error: "Could not determine the dependencies of task ':app:jacocoTestReport'" What I miss? – Borys Jul 17 '14 at 09:35
  • I'm using `classpath 'com.android.tools.build:gradle:0.12.+'`, what's version are you using? – Hieu Rocker Jul 17 '14 at 10:16
  • I use the same classpath 'com.android.tools.build:gradle:0.12.+' ./gradlew --version give me Gradle 1.12 – Borys Jul 17 '14 at 10:21
  • 1
    Hey, I just updated the answer to contain the whole `build.gradle`, hope it help. – Hieu Rocker Jul 17 '14 at 10:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57487/discussion-between-hieu-rocker-and-borys). – Hieu Rocker Jul 17 '14 at 10:48
  • Does anyone know what needs to be done to get it to work for projects that don't use Robolectric but still have a dependency of Dagger? – Shane Doyle Aug 02 '14 at 16:47
  • How do I run the instrumentation tests with this configuration? – Asker Jan 25 '16 at 02:16
  • I have improved gradle script for Jacoco: https://gist.github.com/ultraon/54cca81ca159ed0a4a9ebf62e89c26ba – ultraon Nov 17 '16 at 13:57
  • why you set `testCoverageEnabled false`? @HieuRocker – raditya gumay Mar 13 '18 at 06:31
  • @HieuRocker Don't you just need to run `./gradlew jacocoTestReport` why to run both `./gradlew testDebugUnitTest jacocoTestReport` – Testing Singh Nov 06 '18 at 20:34
7

I'm using JaCoCo with a project using RoboGuice, Butterknife and Robolectric. I was able to set it up using @Hieu Rocker's solution, however there were some minor drawbacks i.e. in our project we use flavors and have some extra tests for those flavors as well as extra java code for each of them. We also use different build types. Therefore a solution to rely on the "testDebug" task was not good enough. Here's my solution: In build.gradle in app module add

apply from: '../app/jacoco.gradle'

Then create a jacoco.gradle file inside of app module with the following content:


    apply plugin: 'jacoco'

    jacoco {
        toolVersion "0.7.1.201405082137"
    }

    def getFlavorFromVariant(String variantName) {
        def flavorString = variantName.replaceAll(/(.*)([A-Z].*)/) { all, flavorName, buildTypeName ->
           flavorName
        }
        return flavorString;
    }

    def getBuildTypeFromVariant(String variantName) {
        def buildTypeString = variantName.replaceAll(/(.*)([A-Z].*)/) { all, flavorName, buildTypeName ->
            "${buildTypeName.toLowerCase()}"
        }
        return buildTypeString;
    }

    def getFullTestTaskName(String variantName) {
        return "test${variantName.capitalize()}UnitTest";
    }

    android.applicationVariants.all { variant ->
        def variantName = variant.name;
        def flavorFromVariant = getFlavorFromVariant("${variantName}");
        def buildTypeFromVariant = getBuildTypeFromVariant("${variantName}");
        def testTaskName = getFullTestTaskName("${variantName}")

        task ("jacoco${variantName.capitalize()}TestReport", type: JacocoReport, dependsOn: testTaskName) {
            group = "Reporting"
            description = "Generate JaCoCo coverage reports after running tests for variant: ${variantName}."
            reports {
                xml.enabled = true
                html.enabled = true
            }

            classDirectories = fileTree(
                    dir: "./build/intermediates/classes/${flavorFromVariant}/${buildTypeFromVariant}",
                    excludes: ['**/R*.class',
                               '**/*$InjectAdapter.class',
                               '**/*$ModuleAdapter.class',
                               '**/*$ViewInjector*.class'
                    ]
            )

            logger.info("Configuring JaCoCo for flavor: ${flavorFromVariant}, buildType: ${buildTypeFromVariant}, task: ${testTaskName}");

            def coverageSourceDirs = [
                    '../app/src/main/java',
                    "../app/src/${flavorFromVariant}/java"
            ]
            sourceDirectories = files(coverageSourceDirs)
            executionData = files("$buildDir/jacoco/${testTaskName}.exec")
            // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
            // We iterate through the compiled .class tree and rename $$ to $.
            doFirst {
                new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
                    if (file.name.contains('$$')) {
                        file.renameTo(file.path.replace('$$', '$'))
                    }
                }
            }
        }
    }

You can execute it from command line like this:

.gradlew jacocoFlavor1DebugTestReport

or

.gradlew jacocoOtherflavorPrereleaseTestReport

In our project we use a convention not to use capital letter inside of flavor and build type names, but if your project does not follow this convention you can simply change getFlavorFromVariant(..) and getBuildTypeFromVariant(..) functions

Hope this helps someone

Piotr Zawadzki
  • 1,678
  • 1
  • 18
  • 24
  • 5
    Hi Piotr, Im getting Could not determine the dependencies of task ':app:jacocoDebugTestReport'. > Task with path 'testDebug' not found in project ':app'. any idea why? – AndroidGecko Aug 21 '15 at 13:51
  • @AndroidGecko I've updated the answer. The test task name now has 'UnitTest' appended at the end. Hope it helps! – Piotr Zawadzki Oct 10 '16 at 08:58
0

You can try to use this Gradle plugin: https://github.com/arturdm/jacoco-android-gradle-plugin

There's a bit more in the answer here https://stackoverflow.com/a/32572259/1711454.

Community
  • 1
  • 1
Artur Stepniewski
  • 1,401
  • 2
  • 11
  • 10
-4

Did you try adding the following:

jacocoTestReport {
   group = "Reporting"
   description = "Generate Jacoco coverage reports after running tests."
   additionalSourceDirs = files(sourceSets.main.allJava.srcDirs)
}

And then instead of running ./gradlew jacocoTestReport run ./gradlew test jacocoTestReport.

If all goes well you should find the test results at build/reports/jacoco/test/html/index.html.

hcpl
  • 17,382
  • 7
  • 72
  • 73
  • 1
    Tried your solution, here's what I get: > Could not find method jacocoTestReport() for arguments [build_5dkpq0odkgno9tsiihqqr1k86u$_run_closure4@49c96202] on project ':App'. – Egor Dec 13 '13 at 14:20
  • 1
    jacoco plugin is not compatible with android plugin. jacoco plugin needs Java project. – skyisle Feb 10 '14 at 02:25
  • So, how to run it in Android project? – Borys Jul 16 '14 at 11:54