Jacoco code coverage in Android Studio with flavors

11,705

Solution 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')
}

Solution 2

Test coverage report using Jacoco with Android Flavors:

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

  1. 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
    
  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
                }
            }
        }
    }
}
  1. run below commands in android studio terminal to build the application

    ./gradlew clean assemble
    
  2. on build successfull , 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
    
  3. 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
    
  4. Now you can open and view the html test coverage file in browser

Share:
11,705

Related videos on Youtube

Adr3nl
Author by

Adr3nl

Updated on June 04, 2022

Comments

  • Adr3nl
    Adr3nl almost 2 years

    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

  • unlimited101
    unlimited101 almost 9 years
    With all these hints I still have got a code coverage of 0% in the test reports although I run through testing an activity. But my reports are located in ${buildDir}/reports/coverage/debug and not in ${buildDir}/reports/jacoco/jacoco.xml or ${buildDir}/jacocoHtml. I already spent a lot of time for integrating Jacoco code coverage in my Instrumentation Tests and I used a lot of descriptions in posts. But still it doesn't run correctly. What's strange is that my coverage.ec file is empty.
  • Theo
    Theo over 8 years
    Should this snippet exclude all Fragments and Activities? There could be code that needs testing in there.
  • Testing Singh
    Testing Singh over 5 years
    @unlimited101 which device do you use? if using samsung it might be a problem. try using HTC, Motorola or else one
  • Tarun
    Tarun about 3 years
    197NODMB25385:app tarun$ ./gradlew testFreeDebugUnitTestCoverage bash: ./gradlew: No such file or directory @JJD