Jacoco code coverage in Android Studio with flavors
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"
-
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
-
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
}
}
}
}
}
-
run below commands in android studio terminal to build the application
./gradlew clean assemble
-
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
-
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
-
Now you can open and view the html test coverage file in browser
Related videos on Youtube
Adr3nl
Updated on June 04, 2022Comments
-
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
-
Robert Rowntree over 9 yearsgithub.com/bumptech/glide/blob/master/library/build.gradle sampl using your tool.
-
-
unlimited101 almost 9 yearsWith 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 mycoverage.ec
file is empty. -
Theo over 8 yearsShould this snippet exclude all Fragments and Activities? There could be code that needs testing in there.
-
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 about 3 years197NODMB25385:app tarun$ ./gradlew testFreeDebugUnitTestCoverage bash: ./gradlew: No such file or directory @JJD