AI 写代码 3 个月后,我在 CI/CD 里加了 5 道防线
【掘金摘要】AI 写的代码单测全绿、上线翻车,问题不在 AI,是你的流水线没设防。5 个配置,照着抄。
AI 编程接入三个月,我们组踩了个大坑:AI 重写过的库存模块,单测全绿、接口全过,上线第二天账对不上。
事后复盘,问题不在 AI 代码有多阴,是我们的 CI/CD 压根没设防。从提交到上线,没有任何一步在验证"业务逻辑对不对"。
这 5 个配置就是踩完坑之后补上的防线,全部能直接抄。目标只有一个:让"逻辑正确性"在 CI 阶段就被验证,而不是等线上数据自己说话。
Step 1:断言业务顺序,别只断言"调用成功"
AI 代码最常见的翻车点:方法调用了、返回值对了,但顺序错了。而大部分测试只断言结果,不锁顺序。
我们原来的用例长这样,AI 特别喜欢这种:
python
def test_deduct_success():
result = order_service.deduct(stock_repo, product_id=1001, qty=2)
assert result.success is True # 只断言调用成功,顺序错了一无所知
改成锁死调用顺序:
python
def test_deduct_checks_stock_before_updating(mocker):
repo = mocker.Mock()
repo.get_stock.return_value = 10
order_service.deduct(repo, product_id=1001, qty=2)
# method_calls 按时间记录所有调用,顺序错直接红
assert repo.method_calls[0][0] == "get_stock", \
f"必须先查库存,实际第一个调用是 {repo.method_calls[0][0]}"
assert repo.method_calls[1][0] == "update_stock"
关键点:mock 住依赖,用 method_calls 断言先做什么、后做什么。凡是涉及"查→改""校验→提交"这种有顺序语义的逻辑,都这么写。
Step 2:属性测试扫边界,AI 想不到的输入它来想
AI 幻觉 bug 很少藏在你手写的样例里,藏在边界和组合里。用 hypothesis 自动生成几百组输入,专治这种。
bash
pip install hypothesis
python
from hypothesis import given, settings, strategies as st
@given(
stock=st.integers(min_value=0, max_value=10_000),
qty=st.integers(min_value=1, max_value=100),
)
@settings(max_examples=300)
def test_deduct_stock_never_negative(stock, qty):
repo = FakeStockRepo(stock)
order_service.deduct(repo, product_id=1001, qty=qty)
assert repo.current() >= 0, f"库存扣成负数了: stock={stock}, qty={qty}"
写的是不变量:库存永远不为负。300 组随机输入跑下来,AI 代码里"库存不足还硬扣"这类问题直接现形。它还会把失败的输入缩到最小,报错信息能直接当 bug 描述用。
Step 3:变异测试拆穿"假覆盖率"
AI 最阴的一手:测试代码和业务代码一起生成,一起错。覆盖率能拉到 95%,全是虚的。
变异测试专治这个:故意把源码改出 bug(叫变异体),看你的用例能不能杀掉它。杀不死的,就是你的测试盲区。
bash
pip install mutmut
# 只变异核心业务模块,别全量跑
mutmut run --paths-to-mutate order_service.py
# 看存活变异体 ------ 活着的都是用例没拦住的问题
mutmut results
跑一次你就知道:原来"95% 覆盖率"的用例,存活变异体能占三成。CI 门槛就定为存活变异体超过 5 个直接失败,逼着把用例补到位。
Step 4:AI 代码自动打标,走强化评审
人不可能逐行审所有代码,但 AI 生成的必须审。先立规矩:AI 生成的关键逻辑,文件头必须写 # GENERATED_BY_AI 标记。然后让 CI 自动认领。
yaml
# .github/workflows/ai-code-gate.yml
name: ai-code-gate
on:
pull_request:
types: [opened, synchronize]
jobs:
tag-ai-code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 扫描 AI 生成标记
id: scan
run: |
if git diff --name-only origin/main...HEAD | xargs grep -l "GENERATED_BY_AI" 2>/dev/null; then
echo "has_ai=true" >> "$GITHUB_OUTPUT"
fi
- name: 打 AI 标签
if: steps.scan.outputs.has_ai == 'true'
run: gh pr edit "${{ github.event.pull_request.number }}" --add-label "ai-generated"
再配分支保护规则:带 ai-generated 标签的 PR,必须 2 人 approve,关键逻辑要开发逐行讲清楚为什么对,讲不明白就打回重写。这条人工闸门看着慢,拦下来的事故比省的这点时间值钱得多。
Step 5:线上对账兜底,别让 bug 在账上躺两周
前四道防线挡住大部分问题,但总有漏网的。最后一道:用数据一致性兜底。
sql
-- 每日对账:订单扣减量必须等于库存流水变动
SELECT o.order_id,
o.qty AS order_qty,
COALESCE(SUM(s.qty_change), 0) AS stock_changed
FROM orders o
LEFT JOIN stock_log s ON s.order_id = o.order_id
WHERE o.created_at >= NOW() - INTERVAL 1 DAY
GROUP BY o.order_id, o.qty
HAVING ABS(stock_changed) <> o.qty;
bash
# crontab -e 每天凌晨 2 点跑,有异常就告警
0 2 * * * /usr/bin/python3 /opt/scripts/stock_reconcile.py >> /var/log/reconcile.log 2>&1
有结果就钉钉告警。逻辑错藏得再深,账对不上是藏不住的。
效果对比(我们团队 3 个月数据)
| 指标 | 引入前 | 引入后 |
|---|---|---|
| 逻辑正确性问题暴露时间 | 平均 3 周(等线上账错) | 上线前拦截约 80% |
| 对账发现脏数据 | 无(靠人工发现) | 上线当天逮住 1 笔 |
| 单测质量 | 95% 覆盖率,变异存活率 30% | 变异存活率 <5% |
最直观的变化:bug 从"线上用户发现"变成"CI 拦住"。测试还是那个测试,工作量一点没少,但活法完全不一样了。
注意事项
- 别指望单个工具全搞定,5 道防线各管一段:顺序、边界、用例质量、评审、兜底
- 变异测试慢,放 nightly 跑,别塞进每次提交
- 打标靠约定,团队没统一规范前,自动化等于空转
- 别一次全上,先拿订单、支付这种核心模块试点,跑顺了再推广
说句实在话:AI 写代码这事儿挡不住,也犯不着挡。把防线修好,比天天防着 AI、抱怨 AI 有用得多。
你们团队给 AI 生成的代码设了什么门槛?覆盖率?评审?还是直接不设防?评论区聊聊,我想看看是不是只有我们组吃过亏。
关注「晴天」,不讲教科书,只聊真实项目里撞过墙才想明白的东西。