Android test code coverage with JaCoCo Gradle plugin

前端 未结 4 1735
说谎
说谎 2020-11-30 03:06

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 pr

相关标签:
4条回答
  • 2020-11-30 03:36

    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.

    0 讨论(0)
  • 2020-11-30 03:38

    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.

    0 讨论(0)
  • 2020-11-30 03:39

    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/

    0 讨论(0)
  • 2020-11-30 03:53

    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

    0 讨论(0)
提交回复
热议问题