把「测试有效性」加进 CI:GitHub Actions 配置指南

一、先看效果

配好之后,每个 PR 会多一个检查:

makefile 复制代码
✅ Test Effectiveness / Check test effectiveness (23s)

变异总数: 20
被杀: 14(测试抓到了)
存活: 6(测试没抓到)

★ 测试有效性: 70%

存活的变异按类型分组:
  边界条件(4 处)⇒ 补「等于/不等于」的情况
  逻辑组合(2 处)⇒ 补「只满足一个条件」的情况

如果有效性 <50%,PR 会变红,不能合并。

二、手把手:5 分钟配好

bash 复制代码
# 1. 在你的项目根目录创建目录
mkdir -p .github/workflows

# 2. 创建文件
touch .github/workflows/mutation.yml

# 3. 粘贴下面的配置

# 4. 提交
git add .github/workflows/mutation.yml
git commit -m "ci: add test effectiveness check"
git push

★ 配好后的效果:下一个 PR 会自动多一个检查。

三、完整配置

把下面这个文件放到 .github/workflows/mutation.yml:

yaml 复制代码
name: Test Effectiveness

on:
  pull_request:
    branches: [main, master]

jobs:
  mutation:
    name: Check test effectiveness
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run mutation test
        id: mutation
        continue-on-error: true
        run: npx ai-test-check ./src --max=20 | tee mutation-report.txt

      - name: Show report
        if: always()
        run: cat mutation-report.txt

      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: mutation-report
          path: mutation-report.txt
          retention-days: 7

      - name: Fail if effectiveness < 50%
        if: steps.mutation.outcome == 'failure'
        run: |
          echo "::error::Test effectiveness is below 50%. Add more edge-case tests."
          exit 1

四、★ 三个关键设计

4.1 为什么用 continue-on-error: true

yaml 复制代码
- name: Run mutation test
  id: mutation
  continue-on-error: true      # ★ 不立刻失败
  run: npx ai-test-check ./src --max=20 | tee mutation-report.txt

原因:

vbnet 复制代码
如果直接失败 ⇒ 后面的「Show report」和「Upload report」不会执行
  ⇒ 开发者看不到报告
  ⇒ 不知道该补什么

用 continue-on-error ⇒ 报告照常生成
  ⇒ 最后一步才决定是否失败
  ⇒ 开发者能看到完整报告

4.2 为什么用 tee

bash 复制代码
npx ai-test-check ./src --max=20 | tee mutation-report.txt

原因:

ini 复制代码
① 输出到终端(开发者能直接看到)
② 同时保存到文件(上传 artifact 用)

★ tee = 一分为二

4.3 为什么最后一步才失败

yaml 复制代码
- name: Fail if effectiveness < 50%
  if: steps.mutation.outcome == 'failure'
  run: |
    echo "::error::Test effectiveness is below 50%."
    exit 1

原因:

vbnet 复制代码
① 前面的步骤用 continue-on-error 兜住
② 报告已生成、已上传
③ 最后才决定 PR 红不红

★ 顺序很重要:先生成报告,再决定失败

五、★ 怎么调阈值

5.1 按项目阶段调

项目阶段 建议阈值
刚引入 30%
有基础 50%(默认)
严格要求 70%

5.2 自定义阈值

ai-test-check 的退出码:

ini 复制代码
0 = 有效性 >= 50%
1 = 有效性 < 50%

如果你想用别的阈值,可以解析输出:

yaml 复制代码
- name: Run mutation test
  id: mutation
  continue-on-error: true
  run: |
    npx ai-test-check ./src --max=20 | tee mutation-report.txt
    RATE=$(grep -oP '测试有效性: \K\d+' mutation-report.txt || echo 0)
    echo "rate=$RATE" >> $GITHUB_OUTPUT
    if [ "$RATE" -lt 70 ]; then exit 1; fi

★ 但我不建议一开始就用高阈值:

erlang 复制代码
新项目:30%
  ⇒ 先让人习惯「有这个检查」
  ⇒ 再慢慢提高

直接 70%:
  ⇒ 大部分 PR 都红
  ⇒ 团队会关掉这个检查

六、★ 只检查改动的文件(性能优化)

问题:全量跑变异测试可能很慢(20 个变异 × 每个跑一次测试)

优化:只测 PR 改动的文件

yaml 复制代码
- name: Get changed files
  id: changed
  run: |
    git diff --name-only origin/${{ github.base_ref }}...HEAD > changed.txt

- name: Run mutation test on changed files
  continue-on-error: true
  run: |
    FILES=$(grep '\.js$' changed.txt | tr '\n' ',' | sed 's/,$//')
    if [ -n "$FILES" ]; then
      npx ai-test-check ./src --file="$FILES" --max=10
    fi

★ 但注意:

css 复制代码
只测改动文件有风险:
  ⇒ 改 A 文件可能影响 B 文件的测试
  ⇒ 但变异测试是「改源码看测试红不红」
  ⇒ 所以只测改动文件通常够用

★ 建议:PR 时只测改动,合并前跑全量

七、★ 常见报错

报错 原因 解决
npx: command not found Node 没装 加 actions/setup-node@v4
❌ 基线测试就是红的 项目测试本来就失败 先修测试
可变异点: 0 个 源码里没有运算符 检查 ./src 路径
⚠ N 个文件解析失败 有些文件是 ES5/TS 正常,跳过
Action 超时 变异太多 减少 --max
报告为空 tee 没写文件 检查路径

八、★ 完整工作流(含缓存)

yaml 复制代码
name: Test Effectiveness

on:
  pull_request:
    branches: [main, master]

jobs:
  mutation:
    name: Check test effectiveness
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'          # ★ 缓存 npm 依赖

      - run: npm ci

      - name: Run mutation test
        id: mutation
        continue-on-error: true
        run: npx ai-test-check ./src --max=20 | tee mutation-report.txt

      - name: Show report
        if: always()
        run: cat mutation-report.txt

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: mutation-report
          path: mutation-report.txt
          retention-days: 7

      - name: Fail if effectiveness < 50%
        if: steps.mutation.outcome == 'failure'
        run: |
          echo "::error::Test effectiveness is below 50%."
          exit 1

★ cache: 'npm' 是关键:

复制代码
第一次跑:下载依赖(慢)
后续跑:从缓存恢复(快)

★ 能省 30-60 秒

九、★ 和覆盖率 CI 的区别

检查 回答什么 什么时候失败
Coverage 代码被执行了吗 覆盖率 < 阈值
Mutation 测试能抓到 bug 吗 有效性 < 阈值

★ 两者互补:

erlang 复制代码
Coverage 100% + 有效性 40%
  ⇒ 每行都跑了,但一半改动测试看不见
  ⇒ 这是「假绿」

Coverage 60% + 有效性 90%
  ⇒ 有些代码没测,但测了的部分是真的
  ⇒ 这比上面健康

★ 建议两个都配:

yaml 复制代码
- name: Coverage
  run: npm test -- --coverage

- name: Mutation
  run: npx ai-test-check ./src --max=20

十、★ 一个反直觉的建议

不要一开始就卡 PR。

erlang 复制代码
阶段 1(第 1 周):只报告,不失败
  ⇒ 让人看到数字
  ⇒ 讨论「这个数字合理吗」

阶段 2(第 2-4 周):卡但阈值低
  ⇒ 阈值 30%
  ⇒ 大部分 PR 能过

阶段 3(第 2 个月):逐步提高
  ⇒ 30% → 40% → 50%

★ 一步到位卡 70% 会让团队直接关掉检查

十一、诚实边界

限制 说明
只支持 JS/CJS 不支持 TS/Python
变异类型有限 13 种运算符 + 5 类字面量
慢 20 个变异 × 每个跑一次测试 = 20-40 秒
单文件 无跨文件契约检测
「100%」不等于完美 只是「这 N 个变异都被抓到了」

★ 最重要的一条 :这个检查不能替代其他测试,只是补上一个盲区。

十二、完整示例

GitHub 仓库 :github.com/moqinghua20...

工作流文件 :.github/workflows/mutation.yml

两个可跑的 demo:

  • examples/calculator/ --- 简单示例(有效性 44%)
  • examples/full/ --- 完整示例(修复前 42%,修复后 100%)

十三、一句话

把「测试有效性」加进 CI,只需 40 行 YAML。关键是:①用 continue-on-error 先出报告再决定失败 ②用 tee 同时输出到终端和文件 ③阈值从 30% 起步,别一步到位。

相关推荐
全栈技术负责人6 小时前
AI 自动化单元测试技术方案
运维·单元测试·自动化
川石课堂软件测试3 天前
涨薪技术|0到1学会性能测试第9课-HTTP响应统计
数据库·功能测试·网络协议·http·kubernetes·单元测试·测试用例
我命由我123454 天前
SQL 检测脚本
java·数据库·sql·功能测试·单元测试·压力测试·可用性测试
神一样的老师7 天前
WS63 嵌入式 SSD1306 OLED 驱动单元测试实战
单元测试·log4j
CesareCheung7 天前
高级软件测试工程师面试锦集
面试·单元测试·压力测试
2501_915918418 天前
在 Xcode 中运行和调试单元测试的完整指南
ide·vscode·macos·ios·单元测试·个人开发·xcode
极小狐9 天前
MR 里测试摘要空白、覆盖率不显示?JUnit 与覆盖率配置 9 步排查
数据库·ci/cd·junit·单元测试·mr·devops·代码覆盖率
toooooop89 天前
FastAdmin(ThinkPHP 5.0)单元测试实践:从零到跑通
单元测试·fastadmin
silvamux_9 天前
大模型 API 单元测试怎么 mock 不误伤
单元测试·log4j·php