Exclude from coverage on multimodule project using jacoco and gradle - android

I've been trying to exclude certain classes and/or packages from the coverage report generated by jacoco. I've worked with jacoco before but it wasn't a multi-module project, so I tried translating the configuration and export it to the modules, but so far the report comes back completely ignoring my exclusion path.
I tried many solutions like:
Filter JaCoCo coverage reports with Gradle
https://gist.github.com/aalmiray/e6f54aa4b3803be0bcac
Unable to Exclude generated classes from kotlin jacoco test coverage
https://github.com/th-deng/jacoco-on-gradle-sample/blob/master/build.gradle.kts
https://github.com/gradle/kotlin-dsl-samples/issues/1176#issuecomment-435816812 <- which is the latest tried I made.
I don't know what I'm doing wrong, I'm not very knowledgable on gradle and almost every documentation/example I find uses a single module project.
I created a small project and pushed to gitlab so you can have a better look.
The desired result would be to be able to add more paths or classes to modules directly.
I would really appreciate any help you can provide.
Code snippets:
Root build.gradle.kts
plugins {
kotlin("jvm") version "1.3.71"
jacoco
}
repositories {
mavenCentral()
}
defaultTasks("clean", "build", "test")
allprojects {
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "jacoco")
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
jacoco {
toolVersion = "0.8.5"
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.testng:testng:6.14.3")
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompile::class) {
kotlinOptions {
jvmTarget = "1.8"
}
}
tasks.test {
useTestNG()
finalizedBy("jacocoTestReport")
}
}
tasks.jacocoTestReport {
val sourceDirs = sourceSets.main.get().allSource.srcDirs
additionalSourceDirs.setFrom(sourceDirs)
sourceDirectories.setFrom(sourceDirs)
classDirectories.setFrom(sourceSets.main.get().output)
reports {
html.isEnabled = true
xml.isEnabled = false
csv.isEnabled = false
}
}
tasks.register<JacocoReport>("jacocoRootReport") {
val sourceDirs = subprojects.map { project -> project.sourceSets.main.get().allSource.srcDirs }
dependsOn(subprojects.map { project -> project.tasks.test })
sourceDirectories.setFrom(sourceDirs)
additionalSourceDirs.setFrom(sourceDirs)
classDirectories.setFrom(subprojects.map { project -> project.sourceSets.main.get().output })
executionData.setFrom(subprojects.map { project -> project.tasks.jacocoTestReport.get().executionData })
reports {
html.isEnabled = true
xml.isEnabled = false
csv.isEnabled = false
}
}
tasks.test {
finalizedBy("jacocoRootReport")
}
Module build.gradle.kts
dependencies {
implementation(project(":api"))
}
tasks {
getByName<JacocoReport>("jacocoTestReport") {
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.map {
fileTree(it) {
exclude("org/example/AppClass.class")
}
}))
}
}
}
Also, any feedback regarding anything else is highly appreciate it, I'm trying to learn/improve and could use some :).
[Edit #1] Just added a singlemodule branch to the same repo in which is working as intended, but in a single module mode.
[Edit #2] Note I'm using jacoco: 0.8.5 gradle: 6.1 with the configuration files on Kotlin DSL (build.gradle.kts)

Some time ago I also was stuck on the process of setting up JaCoCo with Sonarqube and came up with this article which might help you.
Here's the full content of jacoco.gradle file:
apply plugin: 'jacoco'
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*'] // see related issue https://github.com/gradle/gradle/issues/5184#issuecomment-457865951
}
tasks.withType(Test) { jacoco.includeNoLocationClasses = true }
project.afterEvaluate {
(android.hasProperty('applicationVariants')
? android.'applicationVariants'
: android.'libraryVariants').all { variant ->
def variantName = variant.name
def unitTestTask = "test${variantName.capitalize()}UnitTest"
def uiTestCoverageTask = "create${variantName.capitalize()}CoverageReport"
tasks.create(name: "${unitTestTask}Coverage", type: JacocoReport, dependsOn: [
"$unitTestTask",
"$uiTestCoverageTask",
":module1:testDebugUnitTest",
":module2:testDebugUnitTest",
":moduleN:testDebugUnitTest",
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = true
csv.enabled = false
}
def fileFilter = [
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'**/com/example/databinding/*',
'**/com/example/generated/callback/*',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/di/module/*',
'**/*MapperImpl*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*Component*.*',
'**/*BR*.*',
'**/Manifest*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module.*', /* filtering Dagger modules classes */
'**/*Dagger*.*',/* filtering Dagger-generated classes */
'**/*MembersInjector*.*',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
'**/*$Result.*', /* filtering `sealed` and `data` classes */
'**/*$Result$*.*'/* filtering `sealed` and `data` classes */
'**/*Args*.*', /* filtering Navigation Component generated classes */
'**/*Directions*.*' /* filtering Navigation Component generated classes */
]
classDirectories.setFrom(files([
fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}", excludes: fileFilter),
fileTree(dir: "$project.rootDir/module1/build/tmp/kotlin-classes/debug", excludes: fileFilter),
fileTree(dir: "$project.rootDir/module2/build/tmp/kotlin-classes/debug", excludes: fileFilter),
fileTree(dir: "$project.rootDir/moduleN/build/tmp/kotlin-classes/debug", excludes: fileFilter),
]))
def coverageSourceDirs = [
"$project.rootDir/app/src/main/java",
"$project.projectDir/src/${variantName}/java",
"$project.rootDir/module1/src/main/java",
"$project.rootDir/module2/src/main/java",
"$project.rootDir/moduleN/src/main/java"
]
additionalSourceDirs.setFrom(files(coverageSourceDirs))
sourceDirectories.setFrom(files(coverageSourceDirs))
def uiTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/${variantName}AndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/jacoco/${unitTestTask}.exec",
uiTestsData,
"$project.rootDir/module1/build/jacoco/testDebugUnitTest.exec",
"$project.rootDir/module2/build/jacoco/testDebugUnitTest.exec",
"$project.rootDir/moduleN/build/jacoco/testDebugUnitTest.exec"
]))
}
}
}
You can examine fileFilter for further filtering options.
Then inside each module you have to import the jacoco.gradle as such:
apply from: "jacoco.gradle"

Related

JaCoCo Gradle - Exclude classes & override Includes

I am attempting to refine my code coverage in my project, and want to exclude any View classes (as they are not tested/testable) but include any ViewModel classes... but I can't seem to get the wildcard filters to cooperate, it's all or nothing!
def excludedPatterns = [
//... other stuff
'**/*Fragment*.*',
'**/*Activity*.*',
'**/*Adapter*.*',
'**/*View*.*', // <-- this line is excluding classes ending w/ ViewModel
'**/*ViewState*.*',
'**/*ViewHolder*.*',
]
task codeCoverageReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
html.enabled true
}
def debugTree = fileTree(
dir: "$project.buildDir/tmp/kotlin-classes/debug",
excludes: excludedPatterns
)
classDirectories.from = files([debugTree])
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories.from = files([mainSrc])
executionData.from = fileTree(dir: project.buildDir, includes: [
'jacoco/testDebugUnitTest.exec',
'outputs/code-coverage/connected/*coverage.ec'
])
}
I've tried several variations of **/*View*.*', including **/*View.*' and others...
Is there something that I am overlooking?
You can use filter on fileTree.
For example for
src/main/java/View.java
class View {}
src/main/java/ViewModel.java
class ViewModel {}
src/main/java/Example.java
class Example {}
src/test/java/ExampleTest.java
import org.junit.Test;
public class ExampleTest {
#Test
public final void test() {
}
}
and build.gradle
apply plugin: 'java'
apply plugin: 'jacoco'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.13'
}
jacocoTestReport {
classDirectories.setFrom(
fileTree(dir: "build/classes/java/main")
.filter({file -> !file.name.contains('View') || file.name.contains('ViewModel')})
)
}
Execution of
gradle clean test jacocoTestReport
using Gradle 6.2.1 will generate the following report in directory build/reports/jacoco/test/html/ that doesn't contain View, but contains ViewModel and Example:

Jacoco in Android - Enforcing code coverage metrics

Need help on enforcing code coverage metrics. I have based my solution on this blog post.
I have already referred following posts in an attempt to solve this problem.
this series of posts
this one on Jacoco with java project (not android)
this official documentation
this one was supposed to be a straight answer
many more SO questions and blogs
(Really embarrassing - still it looks like I'm missing something :( )
I am able to get the code coverage but not able to enforce minimum threshold.
Here is how my Jacoco.gradle looks like -
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.2"
}
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 ->
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") {
reports {
xml.enabled = true
html.enabled = true
}
group = "Reporting"
description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/javac/${buildTypeName}",
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")
}
}
}
}
I have included the above script in app module's build.gradle.
apply plugin: 'com.android.application'
apply from: '../jacoco.gradle'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.jacocodemo"
minSdkVersion 27
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled = true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
I have tried many different approaches so far. Looks like the Jacoco documentation is biased towards plain java projects as the code snippets almost always need changes when used in android, but even then no luck.
Here's my attempt to solve this -
(just give the Jacoco.gradle here as the main code is in it)
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.2"
}
// ****** THIS IS MY ATTEMPT AT IT [START] ******
task jacocoTestCoverageVerification(type: JacocoCoverageVerification) {
violationRules {
rule {
enabled = true
limit {
minimum = 0.9
}
}
}
}
check.dependsOn jacocoTestCoverageVerification
// ****** THIS IS MY ATTEMPT AT IT [END] ******
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 ->
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") {
reports {
xml.enabled = true
html.enabled = true
}
group = "Reporting"
description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/javac/${buildTypeName}",
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")
}
}
}
}
and then I tried invoking it as follows:
gradle clean build testDebugUnitTestCoverage jacocoTestCoverageVerification.
Any help on this is much appreciated.

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).

Categories

Resources