如何借助Bitbucket实现Pull Request的增量代码覆盖率检测
为Bitbucket PR实现变更代码覆盖率检测与阈值拦截
以下是在Jenkins多分支流水线中实现需求的具体步骤:
1. 获取PR中变更的Java文件
首先需要拿到当前PR分支与目标分支(如main)的差异Java文件,用Git命令即可实现:
# 利用Jenkins自动注入的CHANGE_TARGET变量获取目标分支,过滤出Java文件 git diff origin/${CHANGE_TARGET:-main} --name-only | grep "\.java$" > changed_files.txt
如果PR没有变更Java文件,创建空文件避免后续流程报错:
[ -s changed_files.txt ] || echo "" > changed_files.txt
2. 让JaCoCo仅针对变更文件生成覆盖率报告
JaCoCo支持通过includes参数指定要检测的文件路径,结合Maven/Gradle动态传入变更文件列表:
Maven 配置
在Jenkins流水线中读取变更文件列表,转成JaCoCo支持的格式后传入构建命令:
def changedFiles = readFile('changed_files.txt').split('\n').collect { "**/${it}" }.join(',') sh "./mvnw clean test jacoco:report -Djacoco.includes=${changedFiles}"
Gradle 配置
先在build.gradle中添加JaCoCo任务的参数支持:
jacocoTestReport { dependsOn test reports { xml.required = true // 必须生成XML报告用于后续解析 html.required = true } // 读取流水线传入的includes参数 if (project.hasProperty('jacoco.includes')) { includes = project.property('jacoco.includes').split(',') } }
然后在Jenkins流水线中执行构建:
def changedFiles = readFile('changed_files.txt').split('\n').collect { "**/${it}" }.join(',') sh "./gradlew test jacocoTestReport -Pjacoco.includes=${changedFiles}"
3. 解析覆盖率并校验阈值
生成XML报告后,用Groovy脚本解析变更文件的覆盖率数据,对比设定的阈值:
def jacocoXml = new XmlSlurper().parse(new File('target/site/jacoco/jacoco.xml')) // Maven路径,Gradle为build/reports/jacoco/test/jacocoTestReport.xml def totalLines = 0 def coveredLines = 0 readFile('changed_files.txt').split('\n').each { filePath -> // 将文件路径转为类全限定名 def className = filePath.replace('.java', '').replace('/', '.') def classNode = jacocoXml.classes.class.find { it.@name == className } if (classNode) { def lineCounter = classNode.counter.find { it.@type == 'LINE' } totalLines += lineCounter.@total.toInteger() coveredLines += lineCounter.@covered.toInteger() } } def coverageRate = totalLines > 0 ? (coveredLines / totalLines) * 100 : 100 echo "变更代码覆盖率: ${String.format('%.2f', coverageRate)}%" // 设定最低覆盖率阈值,比如80% def minCoverage = 80 if (coverageRate < minCoverage) { error "变更代码覆盖率 ${String.format('%.2f', coverageRate)}% 低于阈值 ${minCoverage}%,构建终止" }
4. 完整Jenkins流水线示例
把上述步骤整合到Jenkinsfile中,适配PR触发场景:
pipeline { agent any environment { MIN_COVERAGE = 80 // 可根据需求调整阈值 } stages { stage('拉取代码') { steps { checkout scm } } stage('获取变更Java文件') { steps { sh """ git diff origin/${CHANGE_TARGET:-main} --name-only | grep "\\.java$" > changed_files.txt [ -s changed_files.txt ] || echo "" > changed_files.txt """ } } stage('构建并生成覆盖率') { steps { script { def changedFiles = readFile('changed_files.txt').split('\n').collect { "**/${it}" }.join(',') if (changedFiles) { // 这里替换为你的构建命令,Maven或Gradle sh "./mvnw clean test jacoco:report -Djacoco.includes=${changedFiles}" } else { echo "无变更Java文件,跳过覆盖率生成" } } } } stage('校验覆盖率阈值') { steps { script { def changedFiles = readFile('changed_files.txt').split('\n') if (changedFiles && changedFiles[0]) { // 根据构建工具调整XML路径 def jacocoXmlPath = 'target/site/jacoco/jacoco.xml' def jacocoXml = new XmlSlurper().parse(new File(jacocoXmlPath)) def totalLines = 0 def coveredLines = 0 changedFiles.each { filePath -> def className = filePath.replace('.java', '').replace('/', '.') def classNode = jacocoXml.classes.class.find { it.@name == className } if (classNode) { def lineCounter = classNode.counter.find { it.@type == 'LINE' } totalLines += lineCounter.@total.toInteger() coveredLines += lineCounter.@covered.toInteger() } } def coverageRate = totalLines > 0 ? (coveredLines / totalLines) * 100 : 100 echo "变更代码覆盖率: ${String.format('%.2f', coverageRate)}%" if (coverageRate < MIN_COVERAGE.toInteger()) { error "变更代码覆盖率未达标: ${String.format('%.2f', coverageRate)}% < ${MIN_COVERAGE}%" } } else { echo "无变更Java文件,跳过阈值校验" } } } } } post { always { // 可选:在Jenkins中展示全量覆盖率报告 jacoco() } } }
关键注意点
- 确保已安装Jenkins Bitbucket分支源插件,它会自动注入
CHANGE_TARGET等PR相关环境变量。 - 如果需要排除测试类,可在
grep命令中添加-v "Test.java$"过滤。 - Gradle的JaCoCo XML报告路径默认是
build/reports/jacoco/test/jacocoTestReport.xml,需要根据实际情况调整。
内容的提问来源于stack exchange,提问作者Shubham
相关产品推荐
相关产品推荐

