Knowledge

AI 写代码之后,测试自动化反而更重要:把它做成回归闸口

AI 写代码之后,测试自动化反而更重要:把它做成回归闸口

封面图:测试自动化作为回归闸口——像 Code Review 一样前置防回归

AI 写完一个功能你很高兴,两周后另一个任务改了一处公共逻辑,旧功能悄悄坏了——你直到用户报障才发现。这就是回归:缺陷不在「写的时候」,而在「改的时候」。Code Review 是时间点闸口,拦当次拦不住未来;要守未来,需要一套会一直跑的测试闸口。

先纠正一个误解:AI 时代,测试反而更重要

测试自动化在 AI 辅助编程之前,就是质量回归保障最重要的方式之一。AI 来了以后,一种声音是:「代码都不用我写了,测试还要我操心吗?AI 自己会写测试。」这个判断恰好反了——结构性变化指向的是反面:

变化 带来的后果 对测试自动化的影响
你不再逐行写代码 以前你亲手敲下的每一行,脑子里都留着一张「哪些地方会受影响」的隐性依赖图;现在 AI 一次性甩给你一大段 diff,连它动了哪些公共函数都得重读一遍 掌控力下降,而自动化闸口是拿回掌控权成本最低的方式
AI 的改动面更大、更自信 一次任务横跨十几个文件,AI 不会像人一样「顺手改两行就心虚」 回归面积变大,人肉 review 兜不住
AI 很擅长让测试变绿 它会把被测对象 mock 掉、把断言抄成实现、写只跑不断言的冒烟测试,然后告诉你「全部通过」 「全绿」的欺骗性上升,没有规约就会拿到一套虚假安全感

一句话:AI 把「写测试」的成本打下来了,同时把「不写测试」的风险抬上去了。

选型:三层分层,e2e 极度克制

给一个已写完/将写完的任务补测试,三种走法:

方案 优点 缺点 适合
不写测试,全靠人肉 review 零额外成本 只拦当次,拦不住未来回归 一次性、极低风险改动
只写 e2e 最贴近用户视角 慢、脆、维护贵 核心链路极少的关键业务
三层分层(unit + integration + 少量 e2e) 各层互补,回归面全覆盖且成本可控 需想清楚每层边界 会持续演进的功能/模块

最终选三层分层,但 e2e 极度克制:单测与集成构成日常「一直跑」的主体,e2e 只罩住「用户绝对不能丢」的 1–3 条关键路径。

关键增量:人做规划,AI 做执行

把「补测试」拆成两层看:

所以真正的杠杆是:人把规划写死成一份规约,AI 照着执行。 规约解决两个词——全面可控

全面 = 覆盖面清单,不让 AI 自由发挥

不要说「给这个函数补点测试」,要说「按这 7 条逐项补,缺一项在 PR 里说明为什么」:

  1. 正常输入 → 明确的期望值(不是「跑通」)
  2. 边界输入:空 / 0 / 1 / 最大值 / 越界 / 极大极小
  3. 非法输入 → 断言具体的异常类型与消息
  4. 失败路径:依赖超时、部分失败、重试耗尽
  5. 幂等性:重复调用结果一致
  6. 无状态残留:用例可独立、乱序、并发执行,不污染 DB / 文件 / 全局
  7. 契约不变:跨模块/外部依赖的字段名、顺序、错误码、消息格式

可控 = 测试必须能失败(这一条最关键)

一条不会失败的测试,不是保障,是装饰。AI 特别容易产出这类「假绿」测试,要用红线挡住:

测试规约:可贴进仓库的 TESTING_GUIDELINE.md(节选)

# TESTING_GUIDELINE.md (补测试前必读,AI 与人都遵守)

## 分层与配额
- unit 70% / integration 20% / e2e 10%(按用例数,不按重要性)
- e2e 只覆盖 1–3 条「用户绝对不能丢」的关键路径,新增需写明理由

## 覆盖面清单(每个被测单元逐项回答,缺项在 PR 里说明)
1. 正常输入 → 明确期望值
2. 边界:空 / 0 / 1 / 最大 / 越界 / 极大极小
3. 非法输入 → 断言具体异常类型与消息
4. 失败路径:依赖超时、部分失败、重试耗尽
5. 幂等:重复调用结果一致
6. 无状态残留:可独立、乱序、并发执行
7. 契约不变:字段名 / 顺序 / 错误码 / 消息格式

## 有效性红线(违反即打回)
- ❌ 无断言 / 只 assert True / 只打印
- ❌ mock 掉被测对象本身(只允许 mock 外部依赖)
- ❌ 断言直接抄实现(同义反复)
- ❌ sleep(秒) 等待异步结果
- ✅ 交付前做变异自检:改坏实现 → 测试必须变红,否则重写

核心代码:断言式单测覆盖边界

import pytest

def price_after_discount(base: float, pct: float) -> float:
    if pct < 0 or pct > 1:
        raise ValueError("pct must be in [0, 1]")
    return round(base * (1 - pct), 2)

def test_price_boundary():
    assert price_after_discount(100, 0.1) == 90.0      # 正常
    assert price_after_discount(100, 0.0) == 100.0     # 边界:无折扣
    assert price_after_discount(100, 1.0) == 0.0       # 边界:全免
    for bad in (-0.1, 1.1):                            # 非法输入
        with pytest.raises(ValueError):                # 显式断言异常被抛出
            price_after_discount(100, bad)

关键规则:最后一组不是「跑通就好」,而是显式断言异常被抛出——无断言的「跑通」不算守住边界。

变异自检:证明这条测试真的会失败

  def price_after_discount(base: float, pct: float) -> float:
-     if pct < 0 or pct > 1:
-         raise ValueError("pct must be in [0, 1]")
+     pass                       # 变异:故意删掉入参校验
      return round(base * (1 - pct), 2)

# 期望:test_price_boundary 必须 FAIL(红色)
# 若仍然 PASS → 这条测试是「假绿」,重写它,而不是把实现改回去就算完。

这一步是「可控」的落地:它把「我觉得测了」变成「我证明过它会拦住这个错误」。

强制执行的规则

分层与流程:

给 AI 的约束:

效果与收益(定性)

复现要点

总结与延伸

  1. AI 写代码之后,测试自动化比以往更重要——掌控力下降、回归面积变大、「全绿」更会骗人。
  2. 人做规划、AI 做执行:用一份测试规约把「写什么才全面、什么才算有效」定死。
  3. 三层分层、e2e 克制:unit 守逻辑、integration 守契约、e2e 只罩关键路径。
  4. 可控 = 测试必须能失败:变异自检是区分「真保障」与「假绿装饰」的硬标准。
  5. 前置 + 便宜模型跑:测试随 PR 进、执行走 CI,让补测试花得起。

前文可配合阅读:独立 Code Review 前置:把质量成本留在「写完即过」

说明:本文为方法论述,收益为定性观察;量化指标(合并前拦下的回归缺陷数、PR 套件耗时、测试相关 credits 占比、变异自检假绿命中率)建议在实践中回填。TESTING_GUIDELINE.md 为可改写模板,分层配额与覆盖面清单需按项目形态调整。

📖 Read in English

← Back to all articles