跳转到内容

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,即使配置文件设了也可能不生效