跳到主要内容

CI 必需检查

必需检查通过了。测试却根本没运行。

有条件跳过的测试任务如何满足分支保护,以及始终运行的门禁任务如何阻止未经测试的合并。

阅读时间:4分钟作者

一次有用的优化造成了策略漏洞

只有添加 run-tests 标签后,单元测试和 E2E 任务才会启动。这样可以在拉取请求仍处于准备阶段时推迟成本较高的测试套件,但每次合并仍应运行这些测试。分支保护并没有强制执行这一意图。

当任务级条件计算为 false 时,测试任务会被跳过。对于必需检查,GitHub 仍会把已跳过任务的结论视为成功。因此,即使没有执行任何测试命令,拉取请求也可能显示为绿色。

保留任务的条件执行

我保留了该标签,因为它在评审期间确实有用。错误在于把条件测试任务本身设为必需检查;即使任务被跳过,GitHub 也可能判定该检查已满足。

unit-tests:
  if: |
    github.event_name == 'workflow_dispatch' ||
    (github.event.action == 'labeled' && github.event.label.name == 'run-tests') ||
    (github.event.action == 'synchronize' &&
      contains(github.event.pull_request.labels.*.name, 'run-tests'))
  steps:
    - run: pnpm test:unit:coverage

测试任务决定现在是否运行,单独的门禁决定拉取请求能否安全合并。混合这两个决定造成了虚假的绿色状态。

将执行任务与策略分开

我在每个测试任务后添加了一个小型门禁。它通过 needs 声明上游任务,使用 if: always() 运行,并且仅在上游任务结果为 success 时通过。现在,无论测试套件被跳过、取消还是失败,都会阻止合并并显示有用的信息。

ci-gate:
  name: CI Gate (Unit)
  runs-on: ubuntu-latest
  needs: [unit-tests]
  if: always()
  steps:
    - name: Check test results
      run: |
        result="${{ needs.unit-tests.result }}"
        if [[ "$result" != "success" ]]; then
          echo "::error::Unit tests did not pass (result: $result)"
          exit 1
        fi
        echo "Unit tests passed"
上游结果   必需门禁
success    通过
failure    失败
cancelled  失败
skipped    失败,并显示可执行的标签提示

让高成本测试任务保持条件执行

这些门禁位于真实测试套件之前。单元测试覆盖 73 个 Jest 文件,强制执行覆盖率阈值并确保覆盖率只升不降。Playwright 在 Chromium 和 WebKit 中运行,使用四个工作进程、两次 CI 重试和共享的已认证状态。

单元测试和 E2E 测试各自获得一个稳定的门禁名称。即使以后更改测试任务实现,分支保护仍可继续要求这些名称。

把门禁设为必需检查,而不是测试任务

分支保护改为要求 CI Gate (Unit) 和 CI Gate (E2E),而不是条件测试任务。这样便明确了合并约定:预期的测试套件必须成功运行,或者显示可见的失败并说明未运行原因。

  • 如果代码仓库有明确理由,可以让高成本工作保持条件执行。
  • 始终汇总影响合并策略的每个最终任务结果。
  • 在分支保护中要求稳定的聚合器名称。
  • 让跳过验证产生可执行的提示,而不是静默显示为绿色。