使用 JaCoCo 和 Gradle 进行离线检测
Offline instrumentation with JaCoCo and Gradle
我遇到了这里很多人遇到的同样问题,即在使用 Jacoco/Gradle 和 Powermock 时获取正确的代码覆盖率信息。
我已经阅读了这里和其他地方的所有各种线程,并且我已经成功地创建了一个任务(针对 Gradle 6.4),它对我的项目 类 进行了离线检测。作为参考,执行此操作的代码如下:
task instrumentClasses(dependsOn: [ classes, project.configurations.jacocoAnt ]) {
inputs.files classes.outputs.files
File outputDir = new File(project.buildDir, 'instrumented')
outputs.dir outputDir
doFirst {
project.delete(outputDir)
ant.taskdef(
resource: 'org/jacoco/ant/antlib.xml',
classpath: project.configurations.jacocoAnt.asPath,
uri: 'jacoco'
)
def instrumented = false
jacocoOfflineSourceSets.each { sourceSetName ->
if (file(sourceSets[sourceSetName as String].output.classesDirs.singleFile.absolutePath).exists()) {
def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
fileset(dir: sourceSets[sourceSetName as String].output.classesDirs.singleFile, includes: '**/*.class')
}
//Replace the classes dir in the test classpath with the instrumented one
sourceSets.test.runtimeClasspath -= sourceSets[sourceSetName as String].output.classesDirs
sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
instrumented = true
}
}
if (instrumented) {
//Disable class verification based on https://github.com/jayway/powermock/issues/375
test.jvmArgs += '-noverify'
}
}
}
现在,在大多数情况下这似乎工作正常。我已成功验证我的 类 现在已正确检测,并且我看到了 Jacoco 生成的报告,其中包含正确的信息。问题是我的 SonarQube 服务器仍然将有问题的 类 列为未涵盖。关于这个我不知道我需要做什么来解决它。
作为参考,我使用的是以下版本的 sonarqube 插件:
"org.sonarqube" version "2.7"
我的 CI 运行 的 Gradle 任务如下:
- ./gradlew jacocoTestReport sonarqube ${SONAR_GRADLE_EXTRA_PARAMS} -Dsonar.projectKey=${CI_PROJECT_ID} -Dsonar.host.url=${SONAR_URL} -Dsonar.login=${SONAR_LOGIN} -Dsonar.branch.name=${CI_COMMIT_REF_NAME};
我确实知道这一定是 SonarQube 或我 运行 Gradle 任务的配置问题,但我不确定是什么原因造成的。
如果您能够生成聚合的 jacoco 报告(从所有源集中聚合),那么您可以在 运行 的同时在声纳任务中简单地指定它(声纳将只选择准确的覆盖信息jacoco计算)
./gradlew sonarqube -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=XXXX -Dsonar.organization=XXXXX -Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco-report.xml
仅供参考,我正在 build/jacoco-report 创建汇总报告。xml
下面是我的gradle配置(可能对你有用)
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'jacoco'
id "org.sonarqube" version "2.8"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
sourceSets {
intTest {
compileClasspath += sourceSets.main.output + sourceSets.test.output
runtimeClasspath += sourceSets.main.output + sourceSets.test.output
}
}
configurations {
intTestImplementation.extendsFrom testImplementation
intTestRuntimeOnly.extendsFrom testRuntimeOnly
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true //To print logs
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.intTest.output.classesDirs
classpath = sourceSets.intTest.runtimeClasspath
shouldRunAfter test
testLogging.showStandardStreams = true //To print logs
}
jacocoTestReport {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
reports {
xml.enabled true
csv.enabled false
xml.destination file("${buildDir}/jacoco-report.xml")
html.destination file("${buildDir}/jacocoHtml")
}
mustRunAfter(test, integrationTest) // integration tests are required to run before generating the report
}
jacocoTestCoverageVerification {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
violationRules {
rule {
limit {
counter = 'INSTRUCTION'
minimum = 0.94
}
limit {
counter = 'BRANCH'
minimum = 1.0
}
}
}
}
check.dependsOn(integrationTest, jacocoTestCoverageVerification)
tasks.withType(Test) {
finalizedBy jacocoTestReport
}
我遇到了这里很多人遇到的同样问题,即在使用 Jacoco/Gradle 和 Powermock 时获取正确的代码覆盖率信息。
我已经阅读了这里和其他地方的所有各种线程,并且我已经成功地创建了一个任务(针对 Gradle 6.4),它对我的项目 类 进行了离线检测。作为参考,执行此操作的代码如下:
task instrumentClasses(dependsOn: [ classes, project.configurations.jacocoAnt ]) {
inputs.files classes.outputs.files
File outputDir = new File(project.buildDir, 'instrumented')
outputs.dir outputDir
doFirst {
project.delete(outputDir)
ant.taskdef(
resource: 'org/jacoco/ant/antlib.xml',
classpath: project.configurations.jacocoAnt.asPath,
uri: 'jacoco'
)
def instrumented = false
jacocoOfflineSourceSets.each { sourceSetName ->
if (file(sourceSets[sourceSetName as String].output.classesDirs.singleFile.absolutePath).exists()) {
def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
fileset(dir: sourceSets[sourceSetName as String].output.classesDirs.singleFile, includes: '**/*.class')
}
//Replace the classes dir in the test classpath with the instrumented one
sourceSets.test.runtimeClasspath -= sourceSets[sourceSetName as String].output.classesDirs
sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
instrumented = true
}
}
if (instrumented) {
//Disable class verification based on https://github.com/jayway/powermock/issues/375
test.jvmArgs += '-noverify'
}
}
}
现在,在大多数情况下这似乎工作正常。我已成功验证我的 类 现在已正确检测,并且我看到了 Jacoco 生成的报告,其中包含正确的信息。问题是我的 SonarQube 服务器仍然将有问题的 类 列为未涵盖。关于这个我不知道我需要做什么来解决它。
作为参考,我使用的是以下版本的 sonarqube 插件:
"org.sonarqube" version "2.7"
我的 CI 运行 的 Gradle 任务如下:
- ./gradlew jacocoTestReport sonarqube ${SONAR_GRADLE_EXTRA_PARAMS} -Dsonar.projectKey=${CI_PROJECT_ID} -Dsonar.host.url=${SONAR_URL} -Dsonar.login=${SONAR_LOGIN} -Dsonar.branch.name=${CI_COMMIT_REF_NAME};
我确实知道这一定是 SonarQube 或我 运行 Gradle 任务的配置问题,但我不确定是什么原因造成的。
如果您能够生成聚合的 jacoco 报告(从所有源集中聚合),那么您可以在 运行 的同时在声纳任务中简单地指定它(声纳将只选择准确的覆盖信息jacoco计算)
./gradlew sonarqube -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=XXXX -Dsonar.organization=XXXXX -Dsonar.coverage.jacoco.xmlReportPaths=build/jacoco-report.xml
仅供参考,我正在 build/jacoco-report 创建汇总报告。xml
下面是我的gradle配置(可能对你有用)
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'jacoco'
id "org.sonarqube" version "2.8"
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
sourceSets {
intTest {
compileClasspath += sourceSets.main.output + sourceSets.test.output
runtimeClasspath += sourceSets.main.output + sourceSets.test.output
}
}
configurations {
intTestImplementation.extendsFrom testImplementation
intTestRuntimeOnly.extendsFrom testRuntimeOnly
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
testLogging.showStandardStreams = true //To print logs
}
task integrationTest(type: Test) {
testClassesDirs = sourceSets.intTest.output.classesDirs
classpath = sourceSets.intTest.runtimeClasspath
shouldRunAfter test
testLogging.showStandardStreams = true //To print logs
}
jacocoTestReport {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
reports {
xml.enabled true
csv.enabled false
xml.destination file("${buildDir}/jacoco-report.xml")
html.destination file("${buildDir}/jacocoHtml")
}
mustRunAfter(test, integrationTest) // integration tests are required to run before generating the report
}
jacocoTestCoverageVerification {
executionData(file("${project.buildDir}/jacoco/test.exec"), file("${project.buildDir}/jacoco/integrationTest.exec"))
violationRules {
rule {
limit {
counter = 'INSTRUCTION'
minimum = 0.94
}
limit {
counter = 'BRANCH'
minimum = 1.0
}
}
}
}
check.dependsOn(integrationTest, jacocoTestCoverageVerification)
tasks.withType(Test) {
finalizedBy jacocoTestReport
}