I'm currently using a gradle multi module java project with good coverage and sonarqube 6.2 with sonarJava plugin 4.10.0.1026. I'm using Gradle 4.0.1, sonarqube plugin 2.5 and jacoco 0.7.9! The code is java 8.
Because of API driven development the API tests are written as abstract tests in the API projects and called from the implementation projects providing the constructors for the tests.
When analyzing the projekt on the sonarqube server the coverage for the implementation projects is measured correctly but the API projects included in the tests of the IMPL projects are on 0.0% coverage. The coverage results for these projects are ignored.
When simply using the jacoco plugin I was able to get the same behaviour. After doing some research I found a solution to get proper jacoco reports:
task codeCoverageReport(type: JacocoReport) {
description "Creates a unified JaCoCo test report for the project."
// Gather execution data from all subprojects
// (change this if you e.g. want to calculate unit test/integration test coverage separately)
executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")
// Add all relevant sourcesets from the subprojects
subprojects.each {
sourceSets it.sourceSets.main
}
reports {
xml.enabled true
html.enabled true
html.destination file("${buildDir}/reports/jacoco")
csv.enabled false
}
}
// always run the tests before generating the report
codeCoverageReport.dependsOn {
subprojects*.test
}
My current result is the following:
JaCoCo:
JaCoCo (codeCoverageReport-Task)
73% Instruction Coverage
91% Branch Coverage
Sonar
43.1% Line Coverage (only ~30% lines considered in calculation!)
82.1% Condition Coverage (only ~20% conditions covered!)
So the coverage results in sonar are not usable. I have read an post announcing the "sonar.jacoco.reportPaths"-parameter starting with sonar 6.2 and I think java-analyzer 4.4 or sth. like that. When adding this parameter to my gradle build script, the script does not compile anymore. When adding the jacoco .exec files to sonar via sonar project administration nothing changes.
It would be great if there would be a way to manage sonar to calculate the correct coverage.
Thx #Lance Java! He pushed me to a cleaner solution than the one below. If all subprojects have jacoco reports this works aswell. If like me there is only a report in few projects the original solution seems to work better.
apply plugin: 'base'
apply plugin: 'org.sonarqube'
[...]
allprojects {
apply plugin: 'java'
apply plugin: "jacoco"
[...]
test {
[...]
jacoco {
append=true
}
}
}
[...]
task jacocoMerge( type: JacocoMerge ) {
dependsOn( subprojects.jacocoTestReport.dependsOn )
mustRunAfter( subprojects.jacocoTestReport.mustRunAfter )
destinationFile = file( "${buildDir}/jacoco/mergedTests.exec" )
executionData = files( subprojects.jacocoTestReport.executionData )
.filter { jacocoReportFile -> jacocoReportFile.exists() }
}
tasks.sonarqube.dependsOn jacocoMerge
[...]
sonarqube {
properties {
[...]
property "sonar.jacoco.reportPath", "${buildDir}/jacoco/*.exec"
}
}
Original answer:
It took some time to manage to get the correct coverage data to sonar. There were multiple issues to solve. Sometimes Sonar lost track of the jacoco changes in the classes, so the tests needed the parameter:
append=true
This did not do all the work. There was still an issue in collecting the cross-project coverage. Best solution therefore was to force jacoco to write coverage data to a single .exec file and to hand this to sonar.
Final solution looks like this:
apply plugin: 'base'
apply plugin: 'org.sonarqube'
[...]
allprojects {
apply plugin: 'java'
apply plugin: "jacoco"
[...]
test {
[...]
jacoco {
append=true
destinationFile = file( "${rootProject.buildDir}/jacoco/jacocoTest.exec" )
}
}
}
[...]
sonarqube {
properties {
[...]
property "sonar.jacoco.reportPath", "${buildDir}/jacoco/*.exec"
}
}
Now sonar has the correct coverage data for my project. After adding some additional tests this is the result:
Total Coverage 91.6%
Line Coverage 91.7%
Condition Coverage 91.3%
Uncovered Lines 36
Uncovered Conditions 11
Lines to Cover 433
Unit Tests 1,114
Unit Test Errors 0
Unit Test Failures 0
Skipped Unit Tests 0
Unit Test Success (%) 100.0%
Unit Test Duration 4s
Hope this may help some of you... ;)
If your tests are in a different project to the sources that you want coverage reports on then you'll need to set additionalSourceDirs and additionalClassDirs. Eg:
evaluationDependsOn ':foo'
task codeCoverageReport(type: JacocoReport) {
additionalSourceDirs.add project(':foo').sourceSets.main.java.sourceDirectories
additionalClassDirs.add project(':foo').sourceSets.main.output.classesDirs
// etc
}
I'm not sure I understand why it's an issue for only some projects to have jacoco and other projects not. You can use Gradle's rich API's (eg TaskCollection and Project) to find them dynamically.
Eg:
[':project1', ':project3', ':project5'].each {
project(it) {
apply plugin: 'java'
apply plugin: 'jacoco'
}
}
project(':merger') {
Collection<Project> jacocoProjects = allprojects.findAll { it.plugins.hasPlugin('jacoco' }
evaluationDependsOn jacocoProjects
task jacocoMerge(type: JacocoMerge) {
dependsOn jacocoProjects*.tasks.withType(Test)
executionData jacocoProjects*.tasks.withType(Test)
}
task mergedReport(type: JacocoReport) {
dependsOn jacocoMerge
executionData jacocoMerge.destinationFile
sourceDirectories.add(files(jacocoProjects*.sourceSets*.java.srcDirs))
classDirectories.add(files(jacocoProjects*.sourceSets*.output.classesDir))
}
}
Related
Code Coverage is showing 0% on dashboard
build.gradle file
plugins {
id "org.sonarqube" version "2.8"
id "java"
id "idea"
id "jacoco"
}
jacoco {
toolVersion = "0.8.5"
}
jacocoTestReport {
reports {
html.enabled true
xml.enabled true
xml.destination file("${buildDir}/reports/jacoco.xml")
}
}
plugins.withType(JacocoPlugin) {
tasks["test"].finalizedBy 'jacocoTestReport'
}
sonarqube {
properties {
property "sonar.java.coveragePlugin", "jacoco"
property "sonar.host.url", "http://169.254.1.100:9000"
property "sonar.coverage.jacoco.xmlReportPath", "${buildDir}/reports/jacoco.xml"
}
}
repositories {
mavenCentral()
jcenter()
}
dependencies {
// https://mvnrepository.com/artifact/junit/junit
testCompile 'junit:junit:4.12'
}
check.dependsOn jacocoTestReport
Running this command
./gradlew build jacocoTestReport sonarqube
The JacocoTestReport gets generated with the correct code coverage
Sonarqube gradle task produces this log
> Task :sonarqube
SonarScanner will require Java 11 to run starting in SonarQube 8.x
Property 'sonar.jacoco.reportPath' is no longer supported. Use JaCoCo's xml report and sonar-jacoco plugin.
Property 'sonar.jacoco.reportPaths' is no longer supported. Use JaCoCo's xml report and sonar-jacoco plugin.
Been Googling for half a day, and the only real solutions to this problem is the following:
Property 'sonar.jacoco.reportPath' is deprecated. Please use 'sonar.jacoco.reportPaths' instead
This answer here explains the double output of:
Property 'sonar.jacoco.reportPaths' is no longer supported. Use JaCoCo's xml report and sonar-jacoco plugin.
However this seems to not have been added to the gradle plugin as the plugin being used is 2.8, the lastest as of posting.
Is there something I'm missing?
You have to enable XML report property as true.
xml.enabled true
To expand on qasanov's answer, I had to add this to my build.gradle file in order for JaCoCo to generate the XML report, which was then picked up automatically by SonarQube:
jacocoTestReport {
reports {
xml.required = true
}
}
The issue in your configuration is type of the property name. It is sonar.coverage.jacoco.xmlReportPaths and not sonar.coverage.jacoco.xmlReportPath
I am not using the gradle sonar plugin, but using Jenkin Job's -> Execute SonarQube Scanner configuration.
By default Jacoco generates only html files, for SonarQube we need xmlReportPath.
Below code in gradle will enable the xml reporting and will generate the file with default name as jacocoTestReport.xml
jacocoTestReport {
reports {
xml.enabled true
}
}
This generates the following file in Jenkins workspace at location /ws/build/reports/jacoco/test/jacocoTestReport.xml along with
/ws/build/reports/jacoco/html folder which contains all the html file for the coverage reports. This report can be accessed by accessing index.html file located at /ws/build/reports/jacoco/html/index.xml
And path to the Jacoco xml report file to be provided in the below property
sonar.coverage.jacoco.xmlReportPaths=<rootFolder>/build/reports/jacoco/test/jacocoTestReport.xml
This did work for me.
Before this in SonarQube I was not able to see the Coverage and in other project Coverage was shown as 0.0%.
So, in summary SonarQube is not able to see your JaCoCo report file.
Here is part of my build.gradle which I have configured to exclude the classes from code coverage.
test {
jacoco {
append = false
destinationFile = file("$buildDir/reports/jacoco/jacoco.exec")
}
finalizedBy jacocoTestReport
}
jacocoTestReport {
reports {
xml {
enabled true
}
html {
enabled true
}
}
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it,
exclude: ['**/*DAO*.*'])
})
}
}
Here I want to exclude DAO classes in my project, as I am mocking those classes anyways, and I am planning to cover them in DB test cases differently from Unit testing.
When I run below command:
gradle jacocoTestReport
It generates the code coverage report which excludes those classes from code coverage,
${project_dir}\build\reports\jacoco\test\html\index.html
As a reason I have excluded those from gradle build via:
afterEvaluate {
classDirectories = files(classDirectories.files.collect {
fileTree(dir: it,
exclude: ['**/*DAO*.*'])
})
}
But when Jenkins builds the project the code coverage reports generated by jenkins doesn't consider this settings, and the report which is generated by jenkins includes those classes, in coverage report.
Another work around I have tried is to modify the code configuration from 'Post-build Actions' for jacoco plugin in jenkins
I have set my pattern under Exclusions (e.g.: **/*DAO*.*) text field, and this is working fine, test cases are getting excluded from code coverage.
But I wanted to know that is there any way I can avoid configuring jenkins for specifing exclusion patterns and uses the reports genrated by gradle jacocoTestReport on jenkins, because this redudant because I am specifing it in build.gradle and If I have to create multiple branches on jenkins then It is kind of maintainance as well.
According to below post this issue is not yet resolved
https://issues.jenkins-ci.org/browse/JENKINS-15570
Is any one has idea on this?
Note: My Jacoco version is :
jacoco {
toolVersion = "0.7.6.201602180812"
}
and Jacoco plugins version on jenkins is :
2.0.1
Trying to get Code coverage on my Robolectric tests in Android utilising Jacoco but it simply refuses to acknowledge my Robolectric tests when creating the reports.
My jacoco.gradle file is as follows:
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.6.201602180812"
}
project.afterEvaluate {
android.applicationVariants.all { variant ->
def name = variant.name
def testTaskName = "test${name.capitalize()}UnitTest"
tasks.create(name: "${testTaskName}Coverage", type: JacocoReport, dependsOn: "$testTaskName") {
group = "Reporting"
description = "Generate Jacoco coverage reports for the ${name.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${name}",
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
sourceDirectories = files(['src/main/java'].plus(android.sourceSets[name].java.srcDirs))
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
}
}
With this setup I can get Coverage reports but I get 0% coverage despite having Robolectric tests in "src/test/java".
If I add in the following code to that file:
android {
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
I get the following error when Gradle tries to sync:
Error:No such property: includeNoLocationClasses for class:
org.gradle.testing.jacoco.plugins.JacocoTaskExtension_Decorated
I know that I need to have Gradle 2.13 to use this includeNoLocationClasses value so in graddle-wrapper.properties I have the following:
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-2.13-20160228000026+0000-all.zip
I am pretty certain I am running Gradle 2.13 with Android plugin version 1.5
In my apps Gradle file I have the following:
//...
apply from: 'jacoco.gradle'
//..
testOptions {
unitTests.returnDefaultValues = true
}
//...
debug {
testCoverageEnabled true
}
And the command I use to run the coverage is:
./gradlew createDebugCoverageReport
So I am wondering why I get the includeNoLocationClasses error despite using the correct Gradle version? And outside of that maybe I am doing something wrong where Jacoco isn't picking up the Robolectric tests in src/test.java ?
I don't see you build.gradle completely, but to have that flag in you have to:
Use gradle 2.13+
Use jacoco 0.7.6.201602180812
You're sure that you use gradle proper version. So, I think, the issue is only in using wrong jacoco.
Mentioning jacoco {toolVersion = "0.7.6.201602180812"} doesn't influence gradle DSL. You should add newer jacoco plugin:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.jacoco:org.jacoco.core:...'
}
}
And you should apply plugin, which you're already doing:
apply from: 'jacoco'
After such configuraiton you don't need jacoco {toolVersion = "..."} more.
Note: consider to update to newer android gradle plugin, 2.2.x is already stable. jacoco also has newer version already 0.7.7.201606060606
One more note: if you see original issue in Android Studio, check that you use wrapper by default and check that you pointed wrapper to gradle 2.13
I'm using Gradle 1.7 and Jacoco plugin. My project uses Java and Scala plugins.
When I run gradlew -i clean jacocoTestReport
Report is not created and I see in the log
:bl:jacocoTestReport (Thread[Daemon Thread 13,5,main] - start
:bl:jacocoTestReport
Skipping task ':bl:jacocoTestReport' as task onlyIf is false.
:bl:jacocoTestReport SKIPPED
:bl:jacocoTestReport (Thread[Daemon Thread 13,5,main]) - complete
What does it mean? Why report is not created?
The task will only run if coverage data is available. You can make sure of that by also running the test task.
Add the following at a top level to your build.gradle:
test {
finalizedBy jacocoTestReport
}
This means that at the end of the test task the jacocoTestReport task should be run.
You will receive your coverage analysis after run the tests.
None of the above worked for me. What worked for me was the following
Add to the top of my build.gradle:
apply plugin: 'jacoco' // code coverage reports
Add the following as a 'task':
// Generate code coverage reports ... run with jacoco
jacocoTestReport{
additionalSourceDirs = files(sourceSets.main.allJava.srcDirs)
reports {
xml.enabled false
csv.enabled false
html.destination "${buildDir}/reports/jacoco/html"
}
executionData = files('build/jacoco/test.exec')
}
Add the following to your gradle test task:
finalizedBy jacocoTestReport
Then I issued the following command:
gradle run test jacoco
For me the issue was that the executionData.setFrom(executionSource) file path was wrong.
For Spring 2.5 Users, who got stuck with it for hours -just like myself.
I was not having the exec file generated.
And because of that ,
I found that the jacocoTestReport was simply "skipped".
I got it fixed by adding :
apply plugin: 'jacoco'
test {
useJUnitPlatform()
finalizedBy jacocoTestReport // report is always generated after tests run
}
jacocoTestReport {
...
...
...
...
}
That's because I'm using Junit5 with spring boot 2.X
And as of today Junit5 is not by default in the test task,
Hope this helps.
I want to add integration tests to my Gradle build (Version 1.0). They should run separately from my normal tests because they require a webapp to be deployed to localhost (they test that webapp). The tests should be able to use classes defined in my main source set. How do I make this happen?
Update for 2021:
A lot has changed in 8ish years. Gradle continues to be a great tool. Now there's a whole section in the docs dedicated to configuring Integration Tests. I recommend you read the docs now.
Original Answer:
This took me a while to figure out and the online resources weren't great. So I wanted to document my solution.
This is a simple gradle build script that has an intTest source set in addition to the main and test source sets:
apply plugin: "java"
sourceSets {
// Note that just declaring this sourceset creates two configurations.
intTest {
java {
compileClasspath += main.output
runtimeClasspath += main.output
}
}
}
configurations {
intTestCompile.extendsFrom testCompile
intTestRuntime.extendsFrom testRuntime
}
task intTest(type:Test){
description = "Run integration tests (located in src/intTest/...)."
testClassesDir = project.sourceSets.intTest.output.classesDir
classpath = project.sourceSets.intTest.runtimeClasspath
}
Here is how I achieved this without using configurations{ }.
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_6
sourceSets {
integrationTest {
java {
srcDir 'src/integrationtest/java'
}
resources {
srcDir 'src/integrationtest/resources'
}
compileClasspath += sourceSets.main.runtimeClasspath
}
}
task integrationTest(type: Test) {
description = "Runs Integration Tests"
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath += sourceSets.integrationTest.runtimeClasspath
}
Tested using: Gradle 1.4 and Gradle 1.6
This was once written for Gradle 2.x / 3.x in 2016 and is far outdated!!
Please have a look at the documented solutions in Gradle 4 and up
To sum up both old answers (get best and minimum viable of both worlds):
some warm words first:
first, we need to define the sourceSet:
sourceSets {
integrationTest
}
next we expand the sourceSet from test, therefor we use the test.runtimeClasspath (which includes all dependenciess from test AND test itself) as classpath for the derived sourceSet:
sourceSets {
integrationTest {
compileClasspath += sourceSets.test.runtimeClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath // ***)
}
}
note) somehow this redeclaration / extend for sourceSets.integrationTest.runtimeClasspath is needed, but should be irrelevant since runtimeClasspath always expands output + runtimeSourceSet, don't get it
we define a dedicated task for just running integration tests:
task integrationTest(type: Test) {
}
Configure the integrationTest test classes and classpaths use. The defaults from the java plugin use the test sourceSet
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
(optional) auto run after test
integrationTest.dependsOn test
(optional) add dependency from check (so it always runs when build or check are executed)
tasks.check.dependsOn(tasks.integrationTest)
(optional) add java,resources to the sourceSet to support auto-detection and create these "partials" in your IDE. i.e. IntelliJ IDEA will auto create sourceSet directories java and resources for each set if it doesn't exist:
sourceSets {
integrationTest {
java
resources
}
}
tl;dr
apply plugin: 'java'
// apply the runtimeClasspath from "test" sourceSet to the new one
// to include any needed assets: test, main, test-dependencies and main-dependencies
sourceSets {
integrationTest {
// not necessary but nice for IDEa's
java
resources
compileClasspath += sourceSets.test.runtimeClasspath
// somehow this redeclaration is needed, but should be irrelevant
// since runtimeClasspath always expands compileClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
}
}
// define custom test task for running integration tests
task integrationTest(type: Test) {
testClassesDir = sourceSets.integrationTest.output.classesDir
classpath = sourceSets.integrationTest.runtimeClasspath
}
tasks.integrationTest.dependsOn(tasks.test)
referring to:
gradle java chapter 45.7.1. Source set properties
gradle java chapter 45.7.3. Some source set examples
Unfortunatly, the example code on github.com/gradle/gradle/subprojects/docs/src/samples/java/customizedLayout/build.gradle or …/gradle/…/withIntegrationTests/build.gradle seems not to handle this or has a different / more complex / for me no clearer solution anyway!
The nebula-facet plugin eliminates the boilerplate:
apply plugin: 'nebula.facet'
facets {
integrationTest {
parentSourceSet = 'test'
}
}
For integration tests specifically, even this is done for you, just apply:
apply plugin: 'nebula.integtest'
The Gradle plugin portal links for each are:
nebula.facet
nebula.integtest
If you're using
Gradle 5.x, have a look at Documentation Section "Testing Java > Configuring integration tests
Example 14 and 15 for details (both for Groovy and Kotlin DSL, either which one you prefer)
alt: "current" Gradle doc link at 2, but might defer in future, you should have a look at if examples changes)
for Gradle 4 have a look at ancient version 3 which is close near to what #Spina posted in 2012
To get IntelliJ to recognize custom sourceset as test sources root:
plugin {
idea
}
idea {
module {
testSourceDirs = testSourceDirs + sourceSets["intTest"].allJava.srcDirs
testResourceDirs = testResourceDirs + sourceSets["intTest"].resources.srcDirs
}
}
Here's what works for me as of Gradle 4.0.
sourceSets {
integrationTest {
compileClasspath += sourceSets.test.compileClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
}
}
task integrationTest(type: Test) {
description = "Runs the integration tests."
group = 'verification'
testClassesDirs = sourceSets.integrationTest.output.classesDirs
classpath = sourceSets.integrationTest.runtimeClasspath
}
As of version 4.0, Gradle now uses separate classes directories for each language in a source set. So if your build script uses sourceSets.integrationTest.output.classesDir, you'll see the following deprecation warning.
Gradle now uses separate output directories for each JVM language, but this build assumes a single directory for all classes from a source set. This behaviour has been deprecated and is scheduled to be removed in Gradle 5.0
To get rid of this warning, just switch to sourceSets.integrationTest.output.classesDirs instead. For more information, see the Gradle 4.0 release notes.
I gather the documentation wasn't great back in 2012 when this question was asked, but for anyone reading this in 2020+: There's now a whole section in the docs about how to add a source set for integration tests. You really should read it instead of copy/pasting code snippets here and banging your head against the wall trying to figure out why an answer from 2012-2016 doesn't quite work.
The answer is most likely simple but more nuanced than you may think, and the exact code you'll need is likely to be different from the code I'll need. For example, do you want your integration tests to use the same dependencies as your unit tests?
You might also want to configure your tests to run with jUnit:
task integrationTest(type: Test) {
...
useJunitPlatform()
}
I'm new to Gradle, using Gradle 6.0.1 JUnit 4.12. Here's what I came up with to solve this problem.
apply plugin: 'java'
repositories { jcenter() }
dependencies {
testImplementation 'junit:junit:4.12'
}
sourceSets {
main {
java {
srcDirs = ['src']
}
}
test {
java {
srcDirs = ['tests']
}
}
}
Notice that the main source and test source is referenced separately, one under main and one under test.
The testImplementation item under dependencies is only used for compiling the source in test. If your main code actually had a dependency on JUnit, then you would also specify implementation under dependencies.
I had to specify the repositories section to get this to work, I doubt that is the best/only way.