构建和部署自动化只是 CI/CD 的"躯体",而质量门禁(Quality Gate) 才是它的"大脑"------它让流水线有能力判断"这次构建是否值得继续推进"。没有质量门禁的流水线只是一条"传输带",无论代码质量如何都会一路送到生产环境。本文深入讲解如何在 CI/CD 流水线中集成单元测试报告、代码覆盖率检查、SonarQube 代码质量扫描,并配置质量门禁,让不合格的代码在早期被拦截。
一、质量门禁的核心思想
质量门禁的本质是:在流水线的关键节点设置检查点,只有通过所有检查的代码才能进入下一阶段。
典型的质量门禁包括:
单元测试全部通过(无失败用例)
代码覆盖率达标(如 > 80%)
代码质量扫描通过(无阻断级问题)
集成测试通过(与外部服务的交互正常)
这些门禁的价值在于将质量检查左移(Shift Left) ------在代码合并之前、在部署之前发现问题,而非等到生产环境才暴露。
二、单元测试与测试报告
2.1 运行测试并生成报告
不同语言/框架的测试命令和报告格式各不相同,但核心思路一致:
yaml
# Maven (Java) - 生成 JUnit XML 报告
- run: mvn test
# npm (Node.js) - 生成 JUnit 格式报告
- run: npm test -- --reporter=junit
# pytest (Python) - 生成 JUnit XML 报告
- run: pytest --junitxml=test-results.xml
2.2 上传测试报告为 Artifact
将测试报告保存为 Artifact,便于后续查看和归档:
yaml
- name: Upload test reports
if: always() # 即使测试失败也上传
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
target/surefire-reports/
test-results.xml
2.3 在 PR 中展示测试结果
使用第三方 Action(如 EnricoMi/publish-unit-test-result-action)可以将测试结果直接发布到 PR 评论区:
yaml
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: |
target/surefire-reports/*.xml
三、代码覆盖率门禁
代码覆盖率衡量的是"被测试覆盖的代码比例"。设置覆盖率门禁(如"新增代码覆盖率不低于 80%")可以有效防止未测试的代码进入代码库。
3.1 生成覆盖率报告
Java + JaCoCo:
xml
<!-- pom.xml 中配置 JaCoCo 插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
yaml
- run: mvn test jacoco:report
Node.js + Jest:
yaml
- run: npm test -- --coverage
3.2 上传覆盖率报告到 GitHub
GitHub 官方提供了 upload-code-coverage Action,可以将覆盖率报告上传到 GitHub 的 Code Coverage API:
yaml
- name: Upload coverage report
uses: actions/upload-code-coverage@v1
with:
name: code-coverage/jacoco
path: target/site/jacoco/jacoco.xml
注意:该 Action 支持 Cobertura XML 格式的覆盖率报告。
3.3 在 PR 中展示覆盖率变化
使用第三方 Action(如 jacoco-badge-generator)可以在 PR 中生成覆盖率徽章:
yaml
- name: Generate coverage badge
uses: cicirello/jacoco-badge-generator@v2
with:
generate-branches-badge: true
jacoco-csv-file: target/site/jacoco/jacoco.csv
3.4 设置覆盖率门禁(阻断流水线)
如果希望覆盖率不达标时直接让 Job 失败,可以使用 coverage 阈值检查:
yaml
- name: Check coverage threshold
run: |
COVERAGE=$(grep -oP 'Total.*?([0-9.]+)%' target/site/jacoco/index.html | head -1 | grep -oP '[0-9.]+')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ 覆盖率 $COVERAGE% 低于 80% 阈值"
exit 1
fi
echo "✅ 覆盖率 $COVERAGE% 达标"
四、SonarQube 代码质量扫描
SonarQube 是业界最流行的代码质量平台,能够检测代码中的 Bug、漏洞、坏味道和重复代码,并通过质量门禁(Quality Gate) 决定代码是否合格。
4.1 配置 SonarQube 扫描
在 Workflow 中添加 SonarQube 扫描步骤:
yaml
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectKey=my-project
-Dsonar.sources=src
-Dsonar.java.binaries=target/classes
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
4.2 在 Workflow 中检查质量门禁
SonarQube 扫描完成后,可以使用 SonarQube Quality Gate Check Action 来检查质量门禁是否通过:
yaml
- name: SonarQube Quality Gate Check
uses: sonarsource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
如果质量门禁未通过,该 Action 会使 Job 失败,从而阻断流水线。
4.3 在 PR 中展示质量门禁结果
SonarQube 可以将质量门禁结果直接报告到 GitHub PR 中,让开发者一目了然地看到代码质量状态。这个功能在 SonarQube Developer Edition 及以上版本中可用。
五、完整的质量门禁流水线
将以上所有环节整合成一条完整的质量门禁流水线:
yaml
name: Quality Gates Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
quality-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 1. 设置 JDK
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
# 2. 运行单元测试(生成 JUnit 报告和 JaCoCo 覆盖率报告)
- name: Run tests with coverage
run: mvn test jacoco:report
# 3. 上传测试报告(即使失败也上传)
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: target/surefire-reports/
# 4. 上传覆盖率报告到 GitHub
- name: Upload coverage report
uses: actions/upload-code-coverage@v1
with:
name: code-coverage/jacoco
path: target/site/jacoco/jacoco.xml
# 5. 检查覆盖率阈值(门禁1)
- name: Check coverage threshold
run: |
COVERAGE=$(grep -oP 'Total.*?([0-9.]+)%' target/site/jacoco/index.html | head -1 | grep -oP '[0-9.]+')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ 覆盖率 $COVERAGE% 低于 80%"
exit 1
fi
echo "✅ 覆盖率 $COVERAGE% 达标"
# 6. SonarQube 扫描
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
with:
args: >
-Dsonar.projectKey=my-project
-Dsonar.sources=src
-Dsonar.java.binaries=target/classes
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
# 7. SonarQube 质量门禁检查(门禁2)
- name: SonarQube Quality Gate Check
uses: sonarsource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
六、质量门禁的最佳实践
分层设置门禁:
PR 阶段:运行单元测试 + 覆盖率检查(快速反馈)
合并后:运行集成测试 + SonarQube 全量扫描
部署前:运行端到端测试 + 安全扫描
门禁阈值要合理:过高的阈值会导致开发效率下降,过低的阈值则形同虚设。建议从 70% 起步,逐步提升。
测试失败时也要上传报告:使用 if: always() 确保即使测试失败,报告也能被上传,便于排查。
集成测试环境隔离:集成测试应使用独立的测试数据库和依赖服务,避免污染生产数据。
门禁结果可视化:将测试报告、覆盖率报告、SonarQube 结果展示在 PR 页面中,让开发者能直观看到质量状况。
七、小结
质量门禁是流水线的"大脑",让代码在进入下一阶段前接受质量检查
单元测试报告:生成并上传 JUnit/XML 格式报告,便于归档和展示
代码覆盖率:设置覆盖率阈值,不达标则阻断流水线
SonarQube:扫描代码质量,通过 Quality Gate Check Action 检查门禁状态