Ci质量门禁规范
合并、发布前的强制检查项
- [Requirement] 合并到主干分支前,以下门禁项必须全部通过:编译/构建、Lint、格式化检查、单元测试、覆盖率、安全扫描、依赖漏洞扫描。
- [Requirement] 发布到生产前,除上述项外还必须通过:集成测试、端到端测试(如适用)、性能基线测试、人工审批。
- [Standard] 门禁项按执行速度分层:快(<2 分钟)→ 中(<10 分钟)→ 慢(>10 分钟);快速门禁优先执行,失败立即中断,节省 CI 资源。
- [Standard] 覆盖率门禁:整体行覆盖率不低于 80%,增量覆盖率不低于 80%,关键模块不低于 90%。
- [Default] 安全扫描使用
Snyk/Trivy/pip-audit扫描依赖漏洞,高危(Critical/High)漏洞阻断合并,中危(Medium)需标注风险并限期修复。 - [Suggestion] 静态分析(SonarQube/CodeQL)作为建议门禁,代码异味(Code Smell)超阈值时告警但不阻断。
- [Caution] 门禁项必须在 CI 中强制执行,禁止在本地跳过或绕过;本地可预跑但不作为放行依据。
# 门禁项清单(合并到 main 前必须全绿)
## 快速门禁(< 2 分钟)
□ Lint(ruff / eslint / golangci-lint)
□ 格式化检查(black --check / prettier --check / gofmt)
□ 类型检查(mypy --strict / tsc --noEmit)
□ 编译/构建(无错误、无警告)
## 中速门禁(< 10 分钟)
□ 单元测试(全部通过)
□ 覆盖率(整体 ≥80%,增量 ≥80%)
□ 安全扫描(无 Critical/High 漏洞)
□ 依赖漏洞扫描(无未修复的高危漏洞)
## 慢速门禁(> 10 分钟,发布前)
□ 集成测试(全部通过)
□ 端到端测试(核心流程通过)
□ 性能基线测试(无回归)
□ 人工审批(Release Manager + Tech Lead)# 反模式:门禁项缺失
只跑了单元测试,没跑覆盖率和安全扫描就合并。
# 反模式:门禁阈值设为 0
fail_under = 0 # 覆盖率门禁形同虚设
# 反模式:本地跑过就算数
"我本地测试通过了,CI 挂了是环境问题,先合并。"- [Requirement] 门禁执行顺序:代码静态检查 → 编译构建 → 单元测试 → 覆盖率 → 安全扫描 → 集成测试 → 人工审批;前一阶段失败立即中止。
- [Requirement] 每次 PR 提交(含 force push)都必须重新触发完整门禁,禁止复用旧结果。
- [Standard] PR 合并要求:所有门禁通过 + 至少 1 人 Approve + 无未解决的 Blocking 评论 + 分支无冲突。
- [Standard] 发布流程:主干合并 → 打 Tag → 触发发布流水线 → 部署到预发 → 冒烟测试 → 人工确认 → 部署到生产 → 生产冒烟。
- [Default] CI 流水线应在 30 分钟内完成(不含慢速门禁);超时应分析原因并优化(并行化、缓存、增量构建)。
- [Suggestion] 门禁结果以可视化看板展示(状态、耗时、失败原因),便于快速定位。
# PR 合并门禁流程
[提交 PR]
↓
[Lint + Format + Type Check] ──失败──→ 中止,通知作者
↓ 通过
[编译构建] ──失败──→ 中止
↓ 通过
[单元测试 + 覆盖率] ──失败──→ 中止
↓ 通过
[安全扫描] ──失败──→ 中止
↓ 通过
[等待人工评审 Approve]
↓
[合并到 main]# GitHub Actions 示例
name: CI Gate
on:
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ruff check .
- run: black --check .
- run: mypy src/
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pytest --cov=src --cov-fail-under=80
security:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip-audit --desc# 反模式:门禁串行但无 need,实际并行且失败不阻断
jobs:
lint: { ... }
test: { ... } # 没有 needs: lint,lint 失败 test 仍会跑
security: { ... } # 同理
# 反模式:跳过门禁直接合并
# 在分支保护规则中未勾选 "Require status checks to pass"- [Requirement] 门禁失败时,PR 自动标记为不可合并;作者必须修复后重新提交,禁止通过管理员权限强制合并。
- [Requirement] 失败原因必须明确:CI 输出应包含失败的具体文件、行号、错误信息,便于作者定位。
- [Standard] 修复时限:Lint/编译/单元测试失败须在 4 小时内修复;安全漏洞修复按严重等级:Critical 24h、High 72h、Medium 7d。
- [Standard] 若失败由环境问题(CI 故障、依赖服务不可用)导致,可由 CI 管理员重跑;不得由作者随意重跑绕过。
- [Default] 同一 PR 连续 3 次因同一原因失败,作者须在 PR 中说明根因和修复方案。
- [Suggestion] 对 flaky test(偶发失败),须记录 Issue 并在 48 小时内修复或隔离,禁止反复重跑碰运气。
- [Caution] 禁止通过删除测试、降低覆盖率阈值、注释代码等方式”修复”门禁失败。
# 门禁失败处理流程
门禁失败
↓
判断失败类型
├─ 代码问题(Lint/编译/测试/覆盖率)→ 作者修复 → 重新提交
├─ 安全漏洞 → 作者修复或申请豁免 → 重新提交
├─ 环境问题(CI/网络/依赖服务)→ CI 管理员确认并重跑
└─ Flaky Test → 记录 Issue → 48h 内修复或隔离# PR 评论模板(门禁失败自动评论)
🚨 CI 门禁未通过
- ❌ Lint: 2 个错误
- src/user/service.py:45: E501 line too long (121 > 120)
- src/user/service.py:89: F401 'os' imported but unused
- ✅ 编译构建
- ❌ 单元测试: 1 个失败
- tests/test_user.py::test_create_user - AssertionError: ...
- ⏭️ 覆盖率: 已跳过(上游失败)
请修复后重新提交。# 反模式:删除失败的测试
# 原测试失败 → 直接注释掉测试代码 → 门禁通过
# 结果:Bug 未修复,测试覆盖丢失
# 反模式:降低阈值绕过
# 覆盖率 75% < 80% → 把 fail_under 改成 75 → 通过
# 结果:覆盖率门禁形同虚设
# 反模式:反复重跑 flaky test
# 测试偶发失败 → 重跑 5 次直到通过 → 合并
# 结果:不稳定测试流入主干,后续持续干扰- [Requirement] 任何门禁豁免必须经 Tech Lead 或架构师书面批准,记录在 PR 描述或 Issue 中,禁止口头豁免。
- [Requirement] 豁免须说明:豁免项、豁免原因、风险评估、补救措施、豁免有效期。
- [Standard] 可豁免场景:① 第三方依赖漏洞暂无修复版本 ② 历史遗留代码覆盖率不达标(须有分期提升计划)③ 紧急热修复需跳过非核心门禁。
- [Standard] 不可豁免项:编译失败、安全漏洞(Critical/High)、权限校验缺失、数据丢失风险。
- [Default] 豁免有效期最长 7 天,到期自动恢复门禁;需延长须重新申请。
- [Suggestion] 豁免记录定期(每月)回顾,清理已过期或已修复的豁免项。
- [Caution] 禁止以”紧急”为由长期豁免;热修复后 24 小时内须补齐门禁。
<!-- PR 描述中的豁免申请模板 -->
## 门禁豁免申请
**豁免项**: 覆盖率门禁(当前 72%,要求 80%)
**豁免原因**:
本次重构涉及 15 个历史模块,原测试覆盖不足。已制定分期提升计划:
- 第 1 期(本周):补充核心模块测试至 85%
- 第 2 期(下周):补充边缘模块测试至 80%
**风险评估**:
中风险。重构已通过人工评审和集成测试,逻辑正确性有保障。
**补救措施**:
- 创建 Issue #789 跟踪覆盖率提升
- 每日检查覆盖率趋势
**豁免有效期**: 7 天(至 2024-01-15)
**批准人**: @tech-lead# 反模式:无记录的口头豁免
作者:"这个测试环境问题,我跳过了。"
Tech Lead:"行。"
(无书面记录,无法追溯)
# 反模式:豁免无期限
豁免安全扫描漏洞,未设有效期,6 个月后仍未修复。
# 反模式:豁免不可豁免项
"这个 Critical 漏洞先豁免,上线再说。"- [Standard] 以下提供 Python + GitHub Actions 的完整门禁配置示例,其他语言参考对应工具链。
- [Default] 配置文件纳入版本管理,变更须经过评审。
# .github/workflows/ci.yml
name: CI Quality Gate
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
PYTHON_VERSION: "3.12"
jobs:
# 快速门禁
fast-gate:
name: Lint + Format + Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: uv
- run: uv sync --frozen
- name: Lint
run: ruff check .
- name: Format check
run: black --check .
- name: Type check
run: mypy src/
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: uv build
# 中速门禁
test:
name: Unit Test + Coverage
needs: fast-gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: uv
- run: uv sync --frozen
- name: Run tests with coverage
run: |
pytest tests/unit \
--cov=src \
--cov-report=term-missing \
--cov-report=xml \
--cov-fail-under=80
- name: Incremental coverage
run: diff-cover coverage.xml --compare-branch=origin/main --fail-under=80
security:
name: Security Scan
needs: fast-gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- run: uv sync --frozen
- name: Dependency audit
run: uv pip audit
- name: Snyk scan
uses: snyk/actions/python@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
# 慢速门禁(仅 main 分支 / 发布)
integration:
name: Integration Test
needs: [test, security]
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: uv
- run: uv sync --frozen
- run: pytest tests/integration -m integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/testdb# pyproject.toml 相关配置
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "B", "UP"]
[tool.black]
line-length = 120
[tool.mypy]
strict = true
warn_return_any = true
warn_unused_configs = true
[tool.coverage.run]
source = ["src"]
omit = ["src/*/migrations/*", "src/*/schemas/*"]
[tool.coverage.report]
fail_under = 80
show_missing = true# 反模式:未使用缓存,每次全量安装依赖
jobs:
test:
steps:
- run: uv sync # 无 cache,每次重新下载,耗时且不稳定
# 反模式:门禁无 needs 依赖,失败不阻断
jobs:
lint: {}
test: {} # lint 失败 test 仍执行
security: {} # 无依赖关系
# 反模式:覆盖率阈值在 CI 中被覆盖
run: pytest --cov=src # 未加 --cov-fail-under,即使配置文件设了也可能不生效