Using JaCoCo to get project-wide coverage - android

I have a project with two modules, one containing the code (submodule1), and the other containing the unit tests(submodule2). Running coverage tests on submodule1 provides coverage results of 0%, since the methods are supposed to be tested in submodule2. Running coverage tests on submodule2 however only returns coverage for classes defined in that module.
I have been trying to create a project-wide coverage and I've found the following:
subprojects {
apply plugin: 'jacoco'
def coverageSourceDirs = [
'src/main/java'
]
jacocoTestReport {
additionalSourceDirs = files(sourceSets.main.allSource.srcDirs)
sourceDirectories = files(sourceSets.main.allSource.srcDirs)
classDirectories = files(sourceSets.main.output)
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
}
test.finalizedBy(project.tasks.jacocoTestReport)
}
task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
classDirectories = files(subprojects.sourceSets.main.output)
dependsOn = subprojects.test
executionData = files(subprojects.jacocoTestReport.executionData)
reports {
html.enabled = true
xml.enabled = true
}
onlyIf = {
true
}
doFirst {
executionData = files(executionData.findAll {
it.exists()
})
}
}
However I'm unable to run it, since I get this error when trying to run ./gradlew jacocoRootReport:
Could not find method jacocoTestReport() for arguments [build_6nnhcgikryugeat1c382yp5m6$_run_closure3$_closure6#1a3e8e24] on root project 'MyProject'.
How can I fix this?

Related

Reducing duplication in gradle scripts

I have 2 tasks i my build.gradle script in my android project:
task jacocoFooTestReport(type: JacocoReport, dependsOn:'testFooDebugUnitTest') {
def mainSrc = [
"$project.projectDir/src/main/java",
flavorSrcDir
]
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTreeJava, debugTreeKotlin])
executionData.from = executionDataFile
reports {
xml.enabled = true
html.enabled = true
}
}
task jacocoBarTestReport(type: JacocoReport, dependsOn:'testBarDebugUnitTest') {
def mainSrc = [
"$project.projectDir/src/main/java",
flavorSrcDir
]
sourceDirectories.from = files([mainSrc])
classDirectories.from = files([debugTreeJava, debugTreeKotlin])
executionData.from = executionDataFile
reports {
xml.enabled = true
html.enabled = true
}
}
This works fine but the issue is the amount of duplication in the tasks. How can I remove this duplication?
EDIT: To be clear, the lines in both methods from sourceDirectories.from until the end of the method are identical.
I tried to separate it out into its own method but was unsuccessful.

Android check code coverage threshold using jacocoTestCoverageVerification

What i want:
I want to match code coverage threshold to minimum value like 60% etc in Android gradle.
What i have tried
stack overflow question i have looked into
jacoco plugin gradle
Problem i am facing
My Gradle file is written like:
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.6.201602180812"
reportsDir = file("$buildDir/customJacocoReportDir")
}
def coverageSourceDirs = [
'src/main/java',
]
jacocoTestReport {
reports {
xml.enabled false
csv.enabled false
html.destination "${buildDir}/jacocoHtml"
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.5
}
}
rule {
enabled = false
element = 'CLASS'
includes = ['org.gradle.*']
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 0.3
}
}
}
}
Now, When i sync my gradle file i face following erros
Could not find method jacocoTestReport() for arguments [build_5t0t9b9hth6zfihsyl5q2obv8$_run_closure2#41a69b20] on project ':app' of type org.gradle.api.Project.
and if i comment jacocoTestReport task then
Could not find method jacocoTestCoverageVerification() for arguments [build_5t0t9b9hth6zfihsyl5q2obv8$_run_closure2#3da790a8] on project ':app' of type org.gradle.api.Project.
I am not able to understand what exactly going on here. Why jacocoTestCoverageVerification method not in plugin. What i am doing wrong.
Is it something gradle is picking jacoco plugin from android plugin?
I have tried mentioning version of jacoco to 0.6.3 as mentioned in there doc that jacocoTestCoverageVerification method is written above this version.
It'll be very helpful if anybody can sort out this problem.
Let me know in any other info required.
Thanks
This problem keeps haunting me for a few times, every time the keyword android jacocoTestCoverageVerification leads me to this page and gets no answer. Finally, I succeeded make the jacoco work and I'd like to share my solution here.
The reason gradle can't find jacocoTestCoverageVerification and jacocoTestReport is because For projects that also apply the Java Plugin, the JaCoCo plugin automatically adds the following tasks jacocoTestReport and jacocoTestCoverageVerification.
It means for projects that not appling the java Plugin, it will not add jacocoTestReport and jacocoTestCoverageVerification.
So we have to add them yourself.
Follow the link: Code Coverage for Android Testing, we can add the task jacocoTestReport.
And the same way, we also can add the task jacocoTestCoverageVerification.
Full function goes like
// https://engineering.rallyhealth.com/android/code-coverage/testing/2018/06/04/android-code-coverage.html
ext.enableJacoco = { Project project, String variant ->
project.plugins.apply('jacoco')
final capVariant = variant.capitalize()
StringBuilder folderSb = new StringBuilder(variant.length() + 1)
for (int i = 0; i < variant.length(); i++) {
char c = variant.charAt(i)
if (Character.isUpperCase(c)) {
folderSb.append('/')
folderSb.append(Character.toLowerCase(c))
} else {
folderSb.append(c)
}
}
final folder = folderSb.toString()
project.android {
buildTypes {
debug {
testCoverageEnabled true
}
}
testOptions {
unitTests.all {
jacoco {
//You may encounter an issue while getting test coverage for Robolectric tests.
//To include Robolectric tests in the Jacoco report, one will need to set the includeNolocationClasses flag to true.
// This can no longer be configured using the android DSL block, thus we search all tasks of Test type and enable it
includeNoLocationClasses = true
}
}
}
jacoco {
version = '0.8.1'
}
}
project.jacoco {
toolVersion = '0.8.1'
}
project.tasks.create(
name: 'jacocoTestCoverageVerification',
type: JacocoCoverageVerification,
dependsOn: ["test${capVariant}UnitTest",
"create${capVariant}CoverageReport"
]
) {
onlyIf = {
true
}
violationRules {
rule {
limit {
minimum = 0.5
}
}
rule {
enabled = false
element = 'CLASS'
includes = ['org.gradle.*']
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 0.3
}
}
}
def coverageSourceDirs = [
"src/main/java",
"src/main/kotlin"
]
def fileFilter = [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*_MembersInjector.class',
'**/Dagger*Component.class',
'**/Dagger*Component$Builder.class',
'**/*Module_*Factory.class',
'**/*_MembersInjector.class',
'**/Dagger*Subcomponent*.class',
'**/*Subcomponent$Builder.class',
'**/Manifest*.*'
]
def javaClasses = fileTree(
dir: "${project.buildDir}/intermediates/javac/$folder",
excludes: fileFilter
)
def kotlinClasses = fileTree(
dir: "${project.buildDir}/tmp/kotlin-classes/$variant",
excludes: fileFilter
)
group = "Reporting"
description = "Applying Jacoco coverage verification for the ${project.name} with the " +
"$variant variant."
classDirectories = files([javaClasses], [kotlinClasses])
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = fileTree(dir: "${project.buildDir}", includes: [
"jacoco/testDebugUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/*.ec",
"outputs/code_coverage/connected/*.ec" //Check this path or update to relevant path
])
}
project.tasks.create(
name: 'jacocoTestReport',
type: JacocoReport,
dependsOn: ["test${capVariant}UnitTest",
"create${capVariant}CoverageReport"
]
) {
def coverageSourceDirs = [
"src/main/java",
"src/main/kotlin"
]
def fileFilter = [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*_MembersInjector.class',
'**/Dagger*Component.class',
'**/Dagger*Component$Builder.class',
'**/*Module_*Factory.class',
'**/*_MembersInjector.class',
'**/Dagger*Subcomponent*.class',
'**/*Subcomponent$Builder.class',
'**/Manifest*.*'
]
def javaClasses = fileTree(
dir: "${project.buildDir}/intermediates/javac/$folder",
excludes: fileFilter
)
def kotlinClasses = fileTree(
dir: "${project.buildDir}/tmp/kotlin-classes/$variant",
excludes: fileFilter
)
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${project.name} with the " +
"$variant variant."
classDirectories = files([javaClasses], [kotlinClasses])
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = fileTree(dir: "${project.buildDir}", includes: [
"jacoco/testDebugUnitTest.exec",
"outputs/code_coverage/debugAndroidTest/connected/*.ec",
"outputs/code_coverage/connected/*.ec" //Check this path or update to relevant path
])
onlyIf = {
true
}
println project
println "current $project buildDir: $buildDir project buildDir: ${project.buildDir}"
System.out.flush()
reports {
html.enabled = true
html.destination file("reporting/jacocohtml")
}
}
}
Gist version
First of all check you gradle verison:
For projects that also apply the Java Plugin, The JaCoCo plugin automatically adds the following tasks:
gradle 3.3 (jacocoTestReport task)
https://docs.gradle.org/3.3/userguide/jacoco_plugin.html#sec:jacoco_tasks
gradle 3.5 (jacocoTestReport , jacocoTestCoverageVerification task)
https://docs.gradle.org/current/userguide/jacoco_plugin.html#sec:jacoco_tasks
Maybe for earlier versions of gradle you need add jacoco dependencies:
...
dependencies {
сlasspath (
[group: 'org.jacoco', name: 'org.jacoco.agent', version: version: '0.7.7.201606060606'],
[group: 'org.jacoco', name: 'org.jacoco.ant', version: version: '0.7.7.201606060606']
)}
...

Jacoco only cover androidTest directory not test in Android Studio.What should I do?

I have a question that my jacoco can cover androidTest directory not test directory.How can I do make jacoco also covered test directory.
This is my gradle
apply plugin: "jacoco"
buildTypes {
debug{
testCoverageEnabled true
}
}
jacoco {
toolVersion = "0.7.6.201602180812"
reportsDir = file("$buildDir/customJacocoReportDir")}
When I run gradlew createDebugCoverageReport. It worked. But only AndroidTest covered . Junit not. In project of Android Studio, only androidTest directory covered,test directory not covered.
Here is my jacoco setup which covers not only androidTest directory and also able to work with different flavors.
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.7.201606060606"
}
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('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
// If buildtype and flavor combo is not available, skip
if (ignoreBuildFlavor(productFlavorName, buildTypeName)) {
return
}
def sourceName, sourcePath
if (!productFlavorName) {
sourceName = sourcePath = "${buildTypeName}"
} else {
sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
sourcePath = "${productFlavorName}/${buildTypeName}"
}
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*.*',
'**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
'**/*$inlined$*.*', // Kotlin specific, Jacoco can not handle several "$" in class name.
'**/*Module.*', // Modules for Dagger.
'**/*Dagger*.*', // Dagger auto-generated code.
'**/*MembersInjector*.*', // Dagger auto-generated code.
'**/*_Provide*Factory*.*',// Dagger auto-generated code.
'**/*$Icepick*.*']
)
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 {
xml.enabled = true
html.enabled = true
}
}
}
}
And don't forget to add this line to your build.gradle:
apply from: "jacoco.gradle"
Finally,I used android-junit-jacoco-plugin.This plugin solved my problem.But if you use this plugin,the unit test have to be all success.If not,The coverage report will not be created by jacoco.

JaCoCo doesn't work with Robolectric tests

I wanted to generate code coverage reports on my JUnit tests in my android project so I added the JaCoCo gradle plugin. This is my project level build.gradle file:
apply plugin: 'jacoco'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-beta6'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
subprojects { prj ->
apply plugin: 'jacoco'
jacoco {
toolVersion '0.7.6.201602180812'
}
task jacocoReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
group = 'Reporting'
description = 'Generate Jacoco coverage reports after running tests.'
reports {
xml {
enabled = true
destination "${prj.buildDir}/reports/jacoco/jacoco.xml"
}
html {
enabled = true
destination "${prj.buildDir}/reports/jacoco"
}
}
classDirectories = fileTree(
dir: 'build/intermediates/classes/debug',
excludes: [
'**/R*.class',
'**/BuildConfig*',
'**/*$$*'
]
)
sourceDirectories = files('src/main/java')
executionData = files('build/jacoco/testDebugUnitTest.exec')
doFirst {
files('build/intermediates/classes/debug').getFiles().each { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
}
jacoco {
toolVersion '0.7.6.201602180812'
}
task jacocoFullReport(type: JacocoReport, group: 'Coverage reports') {
group = 'Reporting'
description = 'Generates an aggregate report from all subprojects'
//noinspection GrUnresolvedAccess
dependsOn(subprojects.jacocoReport)
additionalSourceDirs = project.files(subprojects.jacocoReport.sourceDirectories)
sourceDirectories = project.files(subprojects.jacocoReport.sourceDirectories)
classDirectories = project.files(subprojects.jacocoReport.classDirectories)
executionData = project.files(subprojects.jacocoReport.executionData)
reports {
xml {
enabled = true
destination "${buildDir}/reports/jacoco/full/jacoco.xml"
}
html {
enabled = true
destination "${buildDir}/reports/jacoco/full"
}
}
doFirst {
//noinspection GroovyAssignabilityCheck
executionData = files(executionData.findAll { it.exists() })
}
}
It works great by running ./gradlew jacocoFullReport. But unfortunately coverage is not reported for the tests that are run with the RobolectricTestRunner (instructions that are obviously called in the tests are not reported as covered). Tests with no #RunWith annotation or run with MockitoJUnitTestRunner report coverage just fine.
Any help would be appreciated to fix this problem.
Update 1: I noticed that I should be using the RobolectricGradleTestRunner. But it didn't help.
It is known issue with the possible workaround - https://github.com/jacoco/jacoco/pull/288
Or downgrade jacoco version to 0.7.1.201405082137
UPDATE
The workaround is not needed anymore. You must use gradle version 2.13 and jacoco version 0.7.6.201602180812.
Update root build.gradle:
buildscript {
dependencies {
classpath 'org.jacoco:org.jacoco.core:0.7.6.201602180812'
}
}
task wrapper( type: Wrapper ) {
gradleVersion = '2.13'
}
Run ./gradlew wrapper
Update project build.gradle:
apply plugin: 'jacoco'
android {
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
The accepted answer is a bit dated. Here is a similar fix we just implemented. In the module (i.e. app) build.gradle add:
apply plugin: 'jacoco'
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
}
This does require JaCoCo 7.6+, but you are likely using it already.
Notes for Studio:
This only fixes the CLI. If you run coverage from Studio using JaCoCo, the Robolectric coverage is still not reported. The default IntelliJ Coverage Runner seems to work fine.
The test were crashing intermittently in Studio unless I added -noverify to the Android JUnit -> VM Options
I was facing the same issue but now it is resolved for me by following this link,
issue link: https://github.com/robolectric/robolectric/issues/2230
Solution for this problem is mentioned here:
https://github.com/dampcake/Robolectric-JaCoCo-Sample/commit/f9884b96ba5e456cddb3d4d2df277065bb26f1d3
I had the same issue. I changed the jacoco plugin version and added includenolocationclasses property. Here is the working jacoco.gradle file (I am using gradle wrapper 2.14.1):
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.6.201602180812"
}
android {
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
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 }
println(buildTypes)
println(productFlavors)
// When no product flavors defined, use empty
if (!productFlavors) productFlavors.add('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
def sourceName, sourcePath
if (!productFlavorName) {
sourceName = sourcePath = "${buildTypeName}"
} else {
sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
sourcePath = "${productFlavorName}/${buildTypeName}"
}
def testTaskName = "test${sourceName.capitalize()}UnitTest"
println("SourceName:${sourceName}")
println("SourcePath:${sourcePath}")
println("testTaskName:${testTaskName}")
// 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")
println("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
}
}
}
This is an old issue, but for those who are still facing, it is worth mentioning that if you are setting up JaCoCo + Robolectric + Espresso - you will indeed be using includeNoLocationClasses, but still will see this error with Java9+ and probably end up here. Add the below snippet to your module build.gradle file
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
change coverage runner to jacoco in android studio
1- select app(root of the project)
2 click on menu (run --> Edit configurations --> code coverage --> choose JaCoCo).

Gradle can't find 'testDebug' task

I'm trying to get Jacoco working with Espresso however when I try to follow an example like
http://raptordigital.blogspot.com/2014/08/code-coverage-reports-using-robolectric.html
The jacocoTestReport task depends on the 'testDebug' task, which Gradle is telling me can't be found in the project. How do I resolve this?
testDebug is the task for your unit tests (given that you are using android gradle plugin v1.2+ or some other unit tests plugin). What you are looking for is connectedAndroidTest task or its flavour.
Here is the complete jacocoTestReport task that works with espresso tests.
apply plugin: 'jacoco'
jacoco {
version "0.7.1.201405082137"
}
task jacocoTestReportAndroidTest(type: JacocoReport, dependsOn: "connectedAndroidTest") {
def coverageSourceDirs = [
'src/main/java'
]
group = "Reporting"
description = "Generates Jacoco coverage reports"
reports {
csv.enabled false
xml{
enabled = true
destination "${buildDir}/reports/jacoco/jacoco.xml"
}
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)
if (project.hasProperty('coverageFiles')) {
// convert the comma separated string to an array to create an aggregate report from
// multiple coverage.ec files
def coverageFilesArray = coverageFiles.split(',')
executionData = files(coverageFilesArray)
}
else {
executionData = files('build/outputs/code-coverage/connected/coverage.ec')
}
}

Categories

Resources