《AI 辅助写单元测试:从零到 80% 覆盖率的实战指南》
引言:很多项目不是没有测试意识,而是测试写起来太费时间:一个函数要补 5 个用例,边界条件想不全,Mock 写起来比业务代码还长。结果就是“先上线、以后再补”,而“以后”永远不会来。大模型最适合干的活之一就是写单元测试:它擅长枚举边界、生成模板化的断言代码、快速搭 Mock。但它也有短板:会臆测业务规则、会写出“自证其证”的假测试、对项目结构一无所知。这篇文章分享一套实战流程:人定规则与边界,AI 负责生成与枚举,把一个模块的覆盖率从 0 拉到 80% 以上。
一、先定规则:测试之前先约法三章
让 AI 动手之前,先把规则写清楚,否则它会按自己的理解“自由发挥”。
- 明确测试框架与风格。比如 Python 用 pytest、断言风格用 assert 而不是 unittest.TestCase、每个用例只测一个行为、命名用 test_行为_条件_预期结果。
- 禁止臆测业务规则。明确要求:“只根据现有代码和注释推断行为,不确定的地方先标注 TODO,不要自己编一个预期值。”
- 边界清单先由人列。比如金额计算:0、负数、最大值、精度舍入;字符串:空串、超长、特殊字符。把清单给 AI,让它按清单生成用例,而不是让它自己想清单。
一个可用的提示词骨架:请为下面这个函数写 pytest 单元测试(粘贴函数代码),要求:只测这个函数的公开行为,不要测私有实现细节;覆盖正常值、0、负数、空输入、超大值;用 pytest + assert 风格,命名 test_行为_条件_预期;不确定的预期值标注 TODO,不要编造;需要 Mock 的地方用 unittest.mock,不要真的发网络请求。
二、三步生成法:从骨架到边界再到 Mock
第 1 步:先让 AI 生成测试骨架。先不追求一次到位。让 AI 按函数签名生成用例骨架:正常路径 1 个、每个边界 1 个。骨架阶段重点看“用例分得对不对”,而不是断言值准不准。
被测函数示例:
def calc_discount(total, user_type):
if total <= 0:
raise ValueError("订单金额必须大于 0")
rate = {"VIP": 0.85, "NEW": 0.9}.get(user_type, 1.0)
discounted = total * rate
return round(discounted - 50, 2) if discounted > 500 else round(discounted, 2)
第 2 步:逐个核对断言值。AI 生成的用例里,最容易错的就是预期值。逐个手算一遍:
def test_calc_discount_vip_over_500():
# 600 * 0.85 = 510,超过 500 再减 50
assert calc_discount(600, "VIP") == 460.0
def test_calc_discount_new_under_500():
# 100 * 0.9 = 90
assert calc_discount(100, "NEW") == 90.0
def test_calc_discount_zero_raises():
with pytest.raises(ValueError):
calc_discount(0, "VIP")
def test_calc_discount_unknown_type_no_discount():
assert calc_discount(100, "GUEST") == 100.0
经验:预期值必须自己算。AI 算错一个小数点,测试就从“守门员”变成了“摆设”,而且是会误导人的摆设。
第 3 步:Mock 外部依赖,只测自己的逻辑。函数里调了数据库、发了 HTTP 请求?测试里一律 Mock 掉,只测自己的逻辑。示例:
with patch("order.send_notify") as mock_notify:
process_order(...)
mock_notify.assert_called_once()
原则:测试失败时,第一时间要能定位是“我的代码错了”还是“外部服务挂了”。Mock 就是这条分界线。
三、覆盖率 80% 的落地打法
- 先测核心路径,再补边角。不要一上来就追求 100%。先覆盖主流程、主要分支、已知的坑。边角分支可以后补。80% 的目标是“核心逻辑都有网兜着”,不是“每一行都有测试”。
- 用覆盖率报告找盲区,而不是凑数字。
pytest --cov=order --cov-report=html,打开 htmlcov/index.html,按“未覆盖行数”排序,从最多的文件开始补。注意:覆盖率是手段不是目的,一行代码被执行过,不等于被验证过——没有断言的测试,覆盖率再高也是 0 分。 - 把“回归用例”变成习惯。每次修 Bug,先写一个能复现 Bug 的失败测试,再修代码让它变绿。这个用例以后就是回归网的一部分。示例:
def test_calc_discount_float_precision():
# 修复记录:0.1 + 0.2 的浮点误差曾导致金额差 1 分钱
assert calc_discount(199.99, "VIP") == 169.99
四、三个常见坑
坑 1:测试里写“自证其证”的断言。比如把 AI 生成的错误预期值直接当成正确值写进去,测试全绿,但测的是错的逻辑。对策:预期值手算,至少抽查核心用例。
坑 2:过度 Mock,把被测对象也 Mock 了。Mock 应该只针对外部依赖,被测函数本身的逻辑不能 Mock。对策:每次写 Mock 前问一句“这个 Mock 掉的,是不是我正在测的东西?”
坑 3:测试代码没人维护。业务改了,测试没改,测试开始大面积飘红,最后被人一键跳过。对策:把测试纳入 Code Review,改业务逻辑必须同步改测试,和改代码同等对待。
五、落地清单
- 测试框架与命名规范已定好,先写进提示词;
- 边界清单由人列出,AI 按清单生成;
- 每个断言的预期值都经过人工核算;
- 外部依赖全部 Mock,失败能定位到自己代码;
- 覆盖率报告按未覆盖行数排序,优先补核心模块;
- 每次修 Bug 先写复现测试,再修代码。
结语:AI 写测试的最大价值不是“快”,而是“全”:它能帮你把想得到的边界都列出来、把模板化的代码都写出来。人要做的,是守住两道关:预期值对不对、边界全不全。这两道关守住了,80% 覆盖率就是水到渠成的事,而不是凑数字的游戏。
- 点赞
- 收藏
- 关注作者
评论(0)