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.
Related
I have been working on an android project in kotlin where I have a few packages: app, package1, package2, package3 each package has its own build.gradle like build.gradle for app, package1, package2, package3 in the respective package.
There is also a top-level build.gradle. Now each package has a src and a tests folder. Now I have created a jacoco.gradle file in the package.root. and applied apply from: "$project.rootDir/jacoco.gradle" in each package's build.gradle.
Here is my jacoco.gradle file for reference.
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.6"
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
jacoco.excludes = ['jdk.internal.*']
}
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"
]) {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"
reports {
html.enabled = true
xml.enabled = false
csv.enabled = false
}
def fileFilter = [
// data binding
'android/databinding/**/*.class',
'**/android/databinding/*Binding.class',
'**/android/databinding/*',
'**/androidx/databinding/*',
'**/BR.*',
// android
'**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*',
// kotlin
'**/*MapperImpl*.*',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/*Component*.*',
'**/*BR*.*',
'**/Manifest*.*',
'**/*$Lambda$*.*',
'**/*Companion*.*',
'**/*Module*.*',
'**/*Dagger*.*',
'**/*MembersInjector*.*',
'**/*_MembersInjector.class',
'**/*_Factory*.*',
'**/*_Provide*Factory*.*',
'**/*Extensions*.*',
// sealed and data classes
'**/*$Result.*',
'**/*$Result$*.*'
]
classDirectories.setFrom(files([
fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}", excludes: fileFilter)
]))
def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
sourceDirectories.setFrom(project.files(variantSourceSets))
def uiTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/${variantName}AndroidTest/connected/", includes: ["**/*.ec"])
executionData(files([
"$project.buildDir/jacoco/${unitTestTask}.exec",
uiTestsData
]))
}
}
}
Now the problem is when I run the testDebugUnitTestCoverage task even though all tests present in a package pass the results show 0% code coverage and all lines are missed. Can someone help me find a solution?
Update
The coverage is reported with app package and package2(without any change in any files) but jacoco still reports 0 coverage for package1 and package3
Edit
added gradle file for 1 of the failing package
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply from: "$project.rootDir/jacoco.gradle"
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
multiDexEnabled true
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
all {
forkEvery = 1
maxParallelForks = Runtime.getRuntime().availableProcessors()
testLogging {
events "passed", "skipped", "failed"
showExceptions = true
exceptionFormat = "full"
showCauses = true
showStackTraces = true
showStandardStreams = false
}
}
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
testCoverageEnabled true
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation(
'androidx.appcompat:appcompat:1.0.2',
'androidx.exifinterface:exifinterface:1.0.0-rc01',
'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03',
'androidx.work:work-runtime-ktx:2.4.0',
'com.google.dagger:dagger:2.24',
'com.google.firebase:firebase-analytics-ktx:17.5.0',
'com.google.firebase:firebase-crashlytics:17.0.0',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
)
testImplementation(
'android.arch.core:core-testing:1.1.1',
'androidx.test.espresso:espresso-core:3.2.0',
'androidx.test.ext:junit:1.1.1',
'androidx.work:work-testing:2.4.0',
'com.google.dagger:dagger:2.24',
'com.google.truth:truth:0.43',
'junit:junit:4.12',
"org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version",
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2',
'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.2',
'org.mockito:mockito-core:2.19.0',
'org.robolectric:robolectric:4.3',
project(":package2"),
)
kapt(
'com.google.dagger:dagger-compiler:2.24',
)
kaptTest(
'com.google.dagger:dagger-compiler:2.24',
)
api project(':package1')
implementation project(':package4')
implementation project(':package3')
}
repositories {
mavenCentral()
}
configurations {
all*.exclude module: 'protobuf-java'
}
update
when I run coverage from run configurations in android studio I get the correct data but when I run it from gradle tasks it shows 0 coverage.
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"
I have an Android app which is made up of 2 modules:
App - UI
Submodule - has most of the business logic
For each of them I have a gradle task to validate code coverage:
App: UI Code coverage (Espresso)
Submodule: Unit tests code coverage
As a requirement for the client, I need to merge those two reports to get the overall/global code coverage of the app.
Note: I'm using Gradle version 3.1.2.
App Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "uiTest"
...
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
debuggable true
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
matchingFallbacks = ['debug']
}
// TESTS
// unitTest will be used to run unit tests.
unitTest.initWith(buildTypes.debug) //Beware the buildType this depends on MUST be above on the gradle file
unitTest {
applicationIdSuffix ".unitTest"
versionNameSuffix "-unitTest"
testCoverageEnabled = true
matchingFallbacks = ['unitTest', 'debug']
}
// uiTest will be used to run ui tests.
uiTest.initWith(buildTypes.debug) //Beware the buildType this depends on MUST be above on the gradle file
uiTest {
applicationIdSuffix ".uiTest"
versionNameSuffix "-uiTest"
testCoverageEnabled = true
matchingFallbacks = ['uiTest', 'debug']
}
...
SubModule Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "uiTest"
buildTypes {
debug {
}
unitTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
uiTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
...
}
I've tried several ways, this one below indeed merges the tests.. but the coverage is not appearing correctly:
The task for creating UI Test coverage in the app:
//UI Test Coverage filtered (we need to run unit tests of App to be able to use Jacoco to filter)
task createTestReport(type: JacocoReport, dependsOn: [':app:testUnitTestUnitTest', ':app:createUiTestAndroidTestCoverageReport']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
//Utils
'**/utils/*.*',
'**/utils/**/*.*'
]
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["**/*.ec"])
def appOtherAndroidTests = fileTree(dir: "${buildDir}/outputs/androidTest-results/connected/", includes: ["**/*.ec"])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec", appAndroidTests, appOtherAndroidTests)
}
The task for creating Unit test coverage in the sub-module:
//Unit Test Coverage filtered
task createTestReport(type: JacocoReport, dependsOn: ['testUnitTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = ['**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'**/*databinding/**/*.*',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
'android/**/*.*',
'**/utils/*.*',
'**/utils/**/*.*']
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec")
}
The task for creating Global coverage in the app:
//Global Test Coverage
task createGlobalTestReport(type: JacocoReport,
dependsOn: [':app:testUnitTestUnitTest', ':app:createTestReport',
':submodule:testUnitTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*',
"**/services/**/model/**",
//Utils
'**/utils/*.*',
'**/utils/**/*.*'
]
// Note: **/reviews/ReviewService*.* was added as BazaarVoice cannot be mocked
//To support Java coverage on Unit tests
def debugAppTree = fileTree(dir: "${buildDir}/intermediates/classes/unitTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def debugKotlinAppTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def debugSdkTree = fileTree(dir: "..//build/intermediates/classes/unitTest", excludes: fileFilter)
def debugKotlinSdkTree = fileTree(dir: "../submodule/build/tmp/kotlin-classes/unitTest", excludes: fileFilter)
def mainAppSrc = "${project.projectDir}/src/main/java"
def debugAppSrc = "${project.projectDir}/src/debug/java"
def mainSdkSrc = "../submodule/src/main/java"
def debugSdkSrc = "../submodule/src/debug/java"
sourceDirectories = files([mainAppSrc, debugAppSrc,
mainSdkSrc, debugSdkSrc])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["**/*.ec"])
def sdkAndroidTests = fileTree(dir: "../submodule/build/outputs/code-coverage/connected/", includes: ["**/*.ec"])
classDirectories = files([debugAppTree, debugSdkTree,
debugKotlinAppTree, debugKotlinSdkTree])
executionData = files("${buildDir}/jacoco/testUnitTestUnitTest.exec"
, "../submodule/build/jacoco/testUnitTestUnitTest.exec"
, appAndroidTests
, sdkAndroidTests
)
}
Any help would be much appreciated
I'm not sure it works with multiple build types.
Try merging it into a single build type and:
App Gradle file:
apply plugin: 'jacoco'
android {
testBuildType "automationTest"
...
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
debuggable true
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
matchingFallbacks = ['debug']
}
// TESTS
automationTest {
applicationIdSuffix ".automationTest"
versionNameSuffix "-automationTest"
testCoverageEnabled = true
matchingFallbacks = ['automationTest', 'debug']
}
...
SubModule Gradle file:
apply plugin: 'jacoco'
android {
buildTypes {
debug {
}
automationTest {
initWith(buildTypes.debug)
testCoverageEnabled = true
}
release {
initWith(buildTypes.debug)
}
...
}
The task for creating Unit test coverage in the sub-module:
task createUnitTestReport(type: JacocoReport, dependsOn: ['testAutomationTestUnitTest']) {
reports {
html.enabled = true
}
def fileFilter = ['**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'**/*databinding/**/*.*',
'**/Manifest*.*',
'**/*Test*.*',
'android/**/*.*']
//To support Java coverage on Unit tests
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/automationTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
def debugSrc = "${project.projectDir}/src/debug/java"
sourceDirectories = files([mainSrc, debugSrc])
classDirectories = files([debugTree], [kotlinDebugTree])
executionData = files("${buildDir}/jacoco/testAutomationTestUnitTest.exec")
}
The task for creating Global coverage in the app:
task createGlobalTestReport(type: JacocoReport,
dependsOn: [':app:createUiTestReport', ':submodule:createUnitTestReport']) {
reports {
html.enabled = true
}
def fileFilter = [
//Android stuff
'**/R.class',
'**/BR.class',
'**/R$*.class',
'**/BR$*.class',
'**/BuildConfig.*',
'android/**/*.*',
//Data Binding
'**/*databinding',
'**/*binders',
'**/*layouts',
'**/Manifest*.*',
'**/*Test*.*'
]
//To support Java coverage on Unit tests
def debugAppTree = fileTree(dir: "${buildDir}/intermediates/classes/automationTest", excludes: fileFilter)
//To support Kotlin coverage on Unit tests
def debugKotlinAppTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def debugSdkTree = fileTree(dir: "../submodule/build/intermediates/classes/automationTest", excludes: fileFilter)
def debugKotlinSdkTree = fileTree(dir: "../submodule/build/tmp/kotlin-classes/automationTest", excludes: fileFilter)
def mainAppSrc = "${project.projectDir}/src/main/java"
def debugAppSrc = "${project.projectDir}/src/debug/java"
def mainSdkSrc = "../submodule/src/main/java"
def debugSdkSrc = "../submodule/src/debug/java"
sourceDirectories = files([mainAppSrc, debugAppSrc,
mainSdkSrc, debugSdkSrc])
classDirectories = files([debugAppTree, debugSdkTree,
debugKotlinAppTree, debugKotlinSdkTree])
def appAndroidTests = fileTree(dir: "${buildDir}/outputs/code-coverage/connected/", includes: ["*.ec"])
executionData = files("${buildDir}/jacoco/testAutomationTestUnitTest.exec"
, "../submodule/build/jacoco/testAutomationTestUnitTest.exec"
, appAndroidTests
)
}
I use a plugin by palantir to create aggregate coverage reports for my different modules - com.palantir.jacoco-full-report
Basically, in your root gradle file, you need to add this in your dependencies:
classpath 'com.palantir:jacoco-coverage:0.4.0'
After that, when you run ./gradlew test jacocoFullReport a test report in build/reports/jacoco/jacocoFullReport/ that evaluates the coverage yielded by all subprojects combined is created.
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.
Below is the build.gradle file
apply plugin: 'android'
apply plugin: 'jacoco'
android {
compileSdkVersion 'Google Inc.:Google APIs:19'
buildToolsVersion "19.1.0"
jacoco {
version = '0.6.2.201302030002'
}
dexOptions {
preDexLibraries = false
}
android.enforceUniquePackageName=false
sourceSets {
androidTest {
java.srcDirs = ['\\src\\androidTest\\java', '\\src\\integTest\\
}
main {
manifest.srcFile('src/main/AndroidManifest.xml')
java.srcDir file('src/main/java')
res.srcDirs = ['src/main/res']
}
}
defaultConfig {
minSdkVersion 9
targetSdkVersion 17
versionCode 35
versionName "3.4.5"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
}
buildTypes {
debug {
testCoverageEnabled = true
}
}
}
Any one have idea what I'm missing.
I'm using this gradle task for JaCoCo reports.
classDirectories -> excludes - look at this line in the task.
def coverageSourceDirs = [
'src/test/java'
]
task jacocoTestReport(type:JacocoReport, dependsOn: "testDebug") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(
dir: "$buildDir/intermediates/classes/debug",
// Specify here files and packages which should be excluded from reports
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
reports {
xml.enabled = true
html.enabled = true
}
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("$buildDir/jacoco/testDebug.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('$$', '$'))
}
}
}
}