Jacoco code coverage in Android Studio with flavor

2019-03-12 10:30发布

问题:

I've been trying to run Jacoco test coverage for quiet some time now. I've tried several possible solutions reported in these topics:

Android test code coverage with JaCoCo Gradle plugin

How do I get a jacoco coverage report using Android gradle plugin 0.10.0 or higher?

Im running the tests in a emulatated device using genymotion. Here is what i added to build.gradle:

apply plugin: 'jacoco'

android{       
    jacoco {
        version "0.7.1.201405082137"
    }        
    buildTypes{
        debug{
                    testCoverageEnabled = true
        }
    }
}

jacoco {
    toolVersion "0.7.1.201405082137"
}

To run it i use something like

./gradlew clean
./gradlew createFLAVOR_NAMEDebugCoverageReport

The relevant generated files/folder are:

/build/intermediates/coverage-instrumented-classes
/build/intermediates/jacoco
/build/outputs/code-coverage/connected/flavors/MyFlavor/coverage.ec

However, there is nothing @ build/reports/jacoco/test/html/index.html or any html page/code coverage report @ /build/outputs.

I've also tried to create a dedicated task to build a coverage report:

def coverageSourceDirs = [
    'src/main/java',
]

task jacocoTestReport(type: JacocoReport, dependsOn: "connectedAndroidTestFLAVOR_NAMEDebug") {
    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/connectedAndroidTestMyFlavorDebug.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('$$', '$'))
            }
        }
    }
}

Then ./gradlew clean and ./gradlew jacocoTestReport. The output is the same as above, so, no html page with coverage report or any other coverage file.

I'm currently using Android Studio v1.0.2 with the latest gradle version. Im fairly new to gradle, so it is possible im missing something basic here.

Thanks

回答1:

After spending the whole day chasing this issue i found out what's the problem. Contrary to the examples i've seen the file generated by the testDebug build is not the .exec file @$buildDir/jacoco/testDebug.exec.

With my gradle and studio version the file generated is a .ec @build/outputs/code-coverage/connected/flavors/myFlavor/coverage.ec

I didn't found any relevant information related to this. It may be a recent change, however, by creating a custom JacocoReport task and changing the executionData variable accordingly i've solved the problem. Here is my implementation:

task jacocoTestReport(type: JacocoReport) {

  def coverageSourceDirs = [
        'src/main/java'
  ]

  group = "Reporting"
  description = "Generates Jacoco coverage reports"
  reports {
      xml{
          enabled = true
          destination "${buildDir}/reports/jacoco/jacoco.xml"
      }
      csv.enabled false
      html{
          enabled true
          destination "${buildDir}/jacocoHtml"
      }
  }

  classDirectories = fileTree(
          dir: 'build/intermediates/classes',
          excludes: ['**/R.class',
                     '**/R$*.class',
                     '**/BuildConfig.*',
                     '**/Manifest*.*',
                     '**/*Activity*.*',
                     '**/*Fragment*.*'
          ]
  )

  sourceDirectories = files(coverageSourceDirs)
  additionalSourceDirs = files(coverageSourceDirs)
  executionData = files('build/outputs/code-coverage/connected/flavors/smartcompanion/coverage.ec')
}


回答2:

Test coverage report using Jacoco with Android Flavors:

Let's consider you have flavors named "free" and "paid"

  • Step1 : Create the file jacoco.gradle in your projects module directory (by default app) where build.gradle exist , it should be next to build.gradle file. directory structure as shown below

    app > jacoco.gradle

  • Step 2: Paste below code in the file which we created in Step 1 , The code has self explanatory comments to understand

apply plugin: 'jacoco'

jacoco {
    toolVersion = "0.7.5.201505241946"
}
project.afterEvaluate {
    // Grab all build types and product flavors
    def buildTypes = android.buildTypes.collect { type ->
        type.name
    }
    def productFlavors = android.productFlavors.collect { flavor ->
        flavor.name
    }
    // When no product flavors defined, use empty
    if (!productFlavors) productFlavors.add('')

    //iterate over the flavors

    productFlavors.each {

        productFlavorName ->
//iterate over build types like debug,release,prod etc.
        buildTypes.each {

            buildTypeName ->
                //sourceName — e.g. freeDebug ,sourcePath — e.g. free/debug
            def sourceName, sourcePath
            if (!productFlavorName) {
                sourceName = sourcePath = "${buildTypeName}"
            } else {
                sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
                sourcePath = "${productFlavorName}/${buildTypeName}"
            }
                // testTaskName —  e.g. testFreeDebugtest task that the coverage task depends on,
            def testTaskName = "test${sourceName.capitalize()}UnitTest"
            // Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest'
            task "${testTaskName}Coverage" (type:JacocoReport, dependsOn: "$testTaskName") {
                group = "Reporting"
                description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."
                classDirectories = fileTree(
                        dir: "${project.buildDir}/intermediates/classes/${sourcePath}",
                        excludes: [
                                '**/R.class',
                                '**/R$*.class',
                                '**/*$ViewInjector*.*',
                                '**/*$ViewBinder*.*',
                                '**/BuildConfig.*',
                                '**/Manifest*.*'
                        ]
                )
                def coverageSourceDirs = [
                        "src/main/java",
                        "src/$productFlavorName/java",
                        "src/$buildTypeName/java"
                ]
                additionalSourceDirs = files(coverageSourceDirs)
                sourceDirectories = files(coverageSourceDirs)
                executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
                reports {
                    //enables and disable the type of file you need
                    xml.enabled = false
                    html.enabled = true
                }
            }
        }
    }
}

  • Step 3: run below commands in android studio terminal to build the application

    ./gradlew clean assemble

  • Step 4: on build sucessfull , run below command to generate the test report (change the string "testFreeDebugUnitTestCoverage " to your particular flavor/build type , for example for paid version command will be ./gradlew testPaidDebugUnitTestCoverage)

    ./gradlew testFreeDebugUnitTestCoverage

  • Step 5 :

It should give success message in terminal , now go to the directory

>app > build > reports >jacoco >${testName} >look for html or xml file report file
  • Step 6: Now you can open and view the html test coverage file in browser

Best of Luck !!!