关于 sonarqube-quality-gate-playbook
该技能为使用 SonarQube 的团队提供质量门禁处理手册,适用于管理单体仓库中的测试覆盖率、缺陷和安全问题。它强调按影响分批处理,并通过分析结果和覆盖率证据验证改进。
主要功能
- 01按优先级处理新增代码问题
- 02诊断并修复 lcov 路径映射
- 03制定测试与覆盖率策略
- 04分批修复缺陷、安全问题和代码异味
- 05用质量门禁及证据确认完成
适用场景
- 01排查 SonarQube 覆盖率低于本地的问题
- 02为后端和前端补充有行为断言的测试
- 03在 CI 中推进质量门禁并汇总结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agustinalbonico/ ai-customizations --skill sonarqube-quality-gate-playbook安装后在 Claude Code 中输入/sonarqube-quality-gate-playbook确认已加载。
详细介绍
SonarQube 质量门禁操作手册
通过小步、可验证的迭代执行此流程。优先考虑实际影响和低风险。
所需输入
在第一个周期开始前定义以下输入:
SONAR_HOST_URL(例如:http://127.0.0.1:9000)- 具有分析权限的
SONAR_TOKEN SONAR_PROJECT_KEY和可选的SONAR_PROJECT_NAMESONAR_NEW_CODE_REFERENCE_BRANCH(默认值:main)- Monorepo 路径:
apps/backend、apps/frontend、packages/* - 各应用(backend/frontend)的测试命令及覆盖率
- 各应用的
lcov.info报告路径 - Sonar 配置文件(
sonar-project.properties)或 CI 中的等效参数 - 覆盖率排除规则和分析排除规则
- Quality Gate 阈值(覆盖率、漏洞、缺陷、代码异味、重复代码)
- 不能破坏的构建/流水线命令
优先级策略
按以下顺序开展工作:
- 优先处理新增代码
- 新增缺陷和漏洞
- 状态为
TO_REVIEW的 Security Hotspots - 关键模块和 PR 涉及模块的覆盖率
- 新增代码异味(优先处理严重程度高的项)
- 活跃区域的重复代码和技术债务
- 历史债务(仅在不影响当前迭代目标时处理)
影响评估启发式
为每个问题或文件简单计算优先级:
priorityScore = (isNewCode*100) + (isBugOrVuln*80) + (isHotspotToReview*70) + severityWeight + (criticalPath*20) + (coverageGap*10) - (estimatedEffort*5)
其中,severityWeight 可以是:blocker 40、critical 30、major 20、minor 10。
防止虚增覆盖率规则
不接受只提高百分比、不验证行为的测试:
- 每个新增测试都必须覆盖正常情况、负面情况和边界情况
- 禁止只使用 snapshot 作为唯一断言
- 避免为没有逻辑的代码编写无意义的测试
- 如果测试无法在真实缺陷出现时失败,就不算有价值的覆盖率
技能流水线(逐步执行)
覆盖率差异操作手册(可复用案例)
当 SonarQube 显示的覆盖率远低于本地结果时,使用此操作手册。
典型症状
- 本地(Jest/Vitest):覆盖率高(例如 >90%)
- SonarQube:覆盖率明显较低(例如 40-60%)
常见根因
- 在 Windows 上生成的
lcov.info中,SF:路径包含反斜杠(\\)。 - SonarQube 在 Linux/容器中运行,无法映射包含
\\的路径。 - 共享包覆盖率较低(例如
packages/utils),拉低了全局覆盖率。
快速诊断
- 检查各应用/包的
lcov.info中开头的SF:条目。 - 检查
sonar-scanner日志,确认是否存在无法映射或被忽略的覆盖率文件。 - 比较各模块(backend/frontend/packages)的覆盖率,找出异常项。
建议命令:
Get-Content "apps\\backend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
Get-Content "apps\\frontend\\coverage\\lcov.info" | Select-String "^SF:" | Select-Object -First 10
如果出现 SF:src\\app.ts 这样的路径,请在扫描前进行规范化。
建议的修复措施
- 在运行
sonar-scanner前,将所有lcov.info中的路径规范化为正斜杠(/)。 - 重新运行覆盖率测试和扫描。
- 提高覆盖率最低的包/模块的覆盖率,避免全局数据产生偏差。
可移植示例(scripts/fix-lcov-paths.js):
const fs = require('node:fs');
const reports = [
'apps/backend/coverage/lcov.info',
'apps/frontend/coverage/lcov.info',
'packages/utils/coverage/lcov.info'
];
for (const reportPath of reports) {
if (!fs.existsSync(reportPath)) continue;
const content = fs.readFileSync(reportPath, 'utf8');
const normalized = content.replace(/\\\\/g, '/');
fs.writeFileSync(reportPath, normalized);
}
CI 防护检查(如果仍有反斜杠则失败):
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en lcov.info"
exit 1
fi
步骤 1:检测当前差距
- 对 backend/frontend/相关 packages 运行覆盖率测试
- 运行 SonarQube 分析
- 获取指标和新增问题,以确定优先级
通用命令:
bun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=coverage,new_coverage,bugs,new_bugs,vulnerabilities,new_vulnerabilities,code_smells,new_code_smells,duplicated_lines_density,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/issues/search?componentKeys=$env:SONAR_PROJECT_KEY&resolved=false&inNewCodePeriod=true&types=BUG,VULNERABILITY,CODE_SMELL&ps=500" -u "$env:SONAR_TOKEN:"
curl -4 -s "$env:SONAR_HOST_URL/api/hotspots/search?projectKey=$env:SONAR_PROJECT_KEY&status=TO_REVIEW&ps=500" -u "$env:SONAR_TOKEN:"
步骤 2:生成或更新 Sonar 配置
在根目录创建或更新 sonar-project.properties,并填写 Monorepo 的实际路径。
步骤 3:确保读取真实覆盖率
- 确认
lcov.info文件存在 - 使用所有路径配置
sonar.javascript.lcov.reportPaths - 检查扫描器日志,确认没有缺少覆盖率报告的警告
- 确认
SF:行使用/,而不是\\ - 如果环境混合使用 Windows/Linux,请在扫描前运行路径规范化
- 确认相关共享包也报告了覆盖率,而不只是应用
步骤 4:以一致的方式运行测试
- 在本地和 CI 中使用相同的命令和标志
- 发布覆盖率制品
- 不要在不同环境中混用运行器和标志
步骤 5:分批迭代
- 批次 A:新增 Bug/漏洞/安全热点
- 批次 B:新代码覆盖率
- 批次 C:新增代码异味
- 批次 D:已修改区域中的重复代码/技术债
后端和前端的引导式测试生成
1)识别未覆盖的文件
- 主要来源:SonarQube(新代码中存在未覆盖行的文件)
- 次要来源:
lcov.info+ PR 中已修改的文件
git diff --name-only origin/main...HEAD | Where-Object { $_ -match '\.(ts|tsx)$' }
2)选择正确的测试类型
- Unit:纯逻辑和依赖较少的函数
- Integration(后端):控制器 + 服务 + 模拟仓库
- Component(前端):交互、状态和无障碍
- E2E:仅用于关键流程,不要用来替代 unit/component 测试
3)Mock 策略
- DB:模拟仓库或隔离的测试数据库
- 外部 HTTP:使用确定性模拟(不调用真实服务)
- 时间/随机数:控制时钟和随机值
- 避免过度使用模拟,以免掩盖真实缺陷
4)测试的最低质量标准
- Arrange/Act/Assert 结构清晰
- 使用有意义的断言检查预期行为
- 必须包含正常、负面和边界用例
- 测试稳定,不会间歇性失败
- 名称应体现行为
5)命名和结构
- 后端:
*.spec.ts - 前端:
*.test.tsx - 推荐模式:
should <结果> when <条件>returns <错误> when <无效输入>
修复代码异味、Bug 和安全问题
采用针对性修复,避免大规模风格调整。
分类检查清单:
- 复杂度:提取函数、使用 early return、减少嵌套
- 重复代码:整合实用工具,避免复制粘贴
- 可空性:添加显式 guard 和安全默认值
- 错误处理:避免空的
catch,在传播错误时提供上下文 - 泄漏/资源:关闭计时器、订阅和连接
- 正则表达式/DoS:避免灾难性模式并限制输入
- 不安全依赖:更新存在漏洞的库
- 清理:验证并清理输入,避免 XSS/注入
- Security Hotspots:逐项审查并记录解决方式
排除规则和权衡
可接受的排除项(需说明理由):
- 没有逻辑的
**/index.ts - 没有分支的简单 DTO 或 types
- 生成的代码(
**/generated/**) - 不包含业务逻辑的最小化启动代码
不可接受的排除项:
- 包含逻辑的服务/用例/控制器
- 包含分支的 hooks 和验证器
- 认证/授权代码
- 为达到百分比而排除代码,却不提升质量
规则:每项排除都必须记录技术原因。
Done 定义和指标
只有具备证据时,才能声明周期已完成:
- Quality Gate 状态为
PASSED new_coverage >= 80new_bugs = 0new_vulnerabilities = 0new_code_smells = 0new_security_hotspots_reviewed = 100%- 重复代码和技术债处于 gate 阈值内
- 构建和流水线均通过
必须附上的最低限度证据:
- 带有
sonar.qualitygate.wait=true的sonar-scanner输出 - 指标/问题/安全热点 API 的输出
- 按文件夹划分的覆盖率(backend/frontend/packages)
- 已解决问题摘要(修复前/后)
配置代码片段
sonar-project.properties
sonar.projectKey=my-org_my-monorepo
sonar.projectName=my-monorepo
sonar.sourceEncoding=UTF-8
sonar.sources=apps/backend/src,apps/frontend/src,packages
sonar.tests=apps/backend,apps/frontend,packages
sonar.test.inclusions=**/*.spec.ts,**/*.test.ts,**/*.test.tsx,**/*.spec.tsx
sonar.javascript.lcov.reportPaths=apps/backend/coverage/lcov.info,apps/frontend/coverage/lcov.info
sonar.newCode.referenceBranch=main
sonar.exclusions=**/dist/**,**/build/**,**/node_modules/**,**/*.d.ts
sonar.coverage.exclusions=**/index.ts,**/*.dto.ts,**/generated/**,**/*.stories.tsx
Jest backend(示例)
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.dto.ts',
'!src/**/index.ts',
'!src/**/generated/**'
]
};
export default config;
Vitest frontend(示例)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: ['src/**/*.stories.tsx', 'src/**/index.ts']
}
}
});
GitHub Actions(示例)
name: quality-gate
on:
pull_request:
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- name: Backend tests with coverage
run: bun run --cwd apps/backend test -- --coverage
- name: Frontend tests with coverage
run: bun run --cwd apps/frontend test -- --coverage
- name: Normalize lcov paths (Windows/Linux safe)
run: node scripts/fix-lcov-paths.js
- name: Validate lcov path format
run: |
if grep -q 'SF:.*\\\\' apps/backend/coverage/lcov.info; then
echo "ERROR: Backslashes encontrados en backend lcov.info"
exit 1
fi
- name: Build
run: bun run build
- name: SonarQube Scan
env:
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
sonar-scanner \
-Dsonar.host.url=$SONAR_HOST_URL \
-Dsonar.token=$SONAR_TOKEN \
-Dsonar.qualitygate.wait=true
GitLab CI(示例)
stages:
- test
- quality
variables:
GIT_DEPTH: "0"
test_and_build:
stage: test
image: oven/bun:1
script:
- bun install --frozen-lockfile
- bun run --cwd apps/backend test -- --coverage
- bun run --cwd apps/frontend test -- --coverage
- node scripts/fix-lcov-paths.js
- bun run build
artifacts:
when: always
paths:
- apps/backend/coverage/
- apps/frontend/coverage/
sonarqube:
stage: quality
image: sonarsource/sonar-scanner-cli:latest
dependencies:
- test_and_build
script:
- sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
allow_failure: false
技能 README
执行时使用这份快速摘要:
- 运行带 coverage 的测试
- 运行 sonar-scanner 并等待质量门禁结果
- 优先级:New Code -> Bugs/Vulns -> Hotspots -> Coverage -> Smells
- 分小批次迭代并附上证据
基本命令:
bun run --cwd apps/backend test -- --coveragebun run --cwd apps/frontend test -- --coveragesonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.qualitygate.wait=true
PR 检查清单
- 没有新增 Bugs 或 Vulnerabilities
- 没有新增 Code Smells
- New Coverage >= 80%
- 已审查新增 Hotspots
- 没有缺乏正当理由的排除项
- 测试涵盖成功、失败和边界情况
- Build 和 CI 均通过
- 已附上 Sonar 证据
-
lcov.info中SF:行不含反斜杠 - 共享 Packages 不存在明显的 coverage 缺口
可直接粘贴的命令模板
$env:SONAR_HOST_URL="http://127.0.0.1:9000"
$env:SONAR_TOKEN="<token>"
$env:SONAR_PROJECT_KEY="<project-key>"
bun run --cwd apps/backend test -- --coverage
bun run --cwd apps/frontend test -- --coverage
bun run build
sonar-scanner `
-Dsonar.host.url=$env:SONAR_HOST_URL `
-Dsonar.token=$env:SONAR_TOKEN `
-Dsonar.projectKey=$env:SONAR_PROJECT_KEY `
-Dsonar.qualitygate.wait=true
curl -4 -s "$env:SONAR_HOST_URL/api/measures/component?component=$env:SONAR_PROJECT_KEY&metricKeys=new_coverage,new_bugs,new_vulnerabilities,new_code_smells,new_duplicated_lines_density" -u "$env:SONAR_TOKEN:"
不确定时可调整的默认值
- New Code 分支:
main - New Coverage 阈值:
80 - new code 最大重复率:
3% - coverage exclusions:仅限不含逻辑的文件
- 批次策略:每个关键类别一个 PR