文章目录
-
- 开篇
- 本文不会讨论什么
- 一、覆盖率高,为什么仍然可能没有覆盖风险
- 二、先从需求和风险生成测试场景
- 三、边界不是“传个 null”这么简单
-
- 1. 输入边界
- 2. 状态边界
- 3. 时间边界
- 4. 依赖边界
- 5. 并发边界
- 四、让 AI 写测试前,先告诉它“应该观察什么”
-
- 1. 返回值
- 2. 数据状态
- 3. 依赖调用
- 4. 消息和事件
- 5. 日志和审计
- 五、AI 生成单测时,最容易出现的 6 个问题
-
- 1. 测试和实现绑定得太紧
- 2. 只验证“调用了”,不验证“调用是否正确”
- 3. Mock 返回值过于理想
- 4. 断言过于宽松
- 5. 测试数据没有表达业务含义
- 6. 测试只验证当前实现,没有验证需求
- 六、用一个真实场景设计测试矩阵
- 七、让 AI 先生成测试设计,再生成测试代码
-
- 第一轮:测试设计
- 第二轮:生成测试代码
- 八、把线上故障转成回归测试
- 九、单测、集成测试和端到端测试怎么分工
- 十、如何判断一组 AI 生成的测试是否有价值
- 十一、我的 AI 单测 Prompt
- 十二、总结
✍创作者:全栈弄潮儿²⁰²⁶
🏡 个人主页:全栈弄潮儿²⁰²⁶
📙 专栏地址:AI 编程进阶实战

开篇
让 AI 写单测,最常见的用法是:
请为这个方法生成单元测试。
几秒钟后,AI 可能会生成一组看起来很完整的测试:
- 一个正常输入测试。
- 一个空值测试。
- 一个异常测试。
- 一个成功返回测试。
测试数量不少,代码也能运行。
但这并不代表测试真的有价值。
很多测试只是把实现代码重新描述了一遍:
输入有效参数
调用方法
断言返回成功
它们可能让覆盖率上升,却没有回答真正重要的问题:
- 业务规则是否被验证?
- 状态不允许时是否会拒绝操作?
- 重复请求会不会产生重复数据?
- 外部依赖失败后,数据是否保持一致?
- 权限不足时,敏感操作是否被阻止?
- 修复一个线上问题后,测试能否防止它再次出现?
上一篇文章,我们讨论了如何把排障上下文交给 AI。
排障结束后,最有价值的动作之一,就是把已经确认的故障条件转成自动化测试。
这篇文章重点讨论:
如何让 AI 帮你设计真正覆盖边界的单测,而不是帮你凑出一个漂亮的覆盖率数字。
本文不会讨论什么
单元测试不是测试工作的全部,也不是覆盖率越高越好。
本文不会:
- 认为 100% 覆盖率就代表代码没有问题。
- 建议为每一行代码机械地生成一个测试。
- 用 Mock 代替所有真实依赖验证。
- 认为 AI 生成的测试天然符合业务预期。
- 把测试数量、断言数量和测试价值画等号。
本文关注的是一套更实用的思路:
先识别业务风险
↓
再列出边界场景
↓
设计可观察结果
↓
让 AI 生成测试
↓
执行并审查测试
一、覆盖率高,为什么仍然可能没有覆盖风险
假设有这样一个下单方法:
public OrderResult createOrder(CreateOrderCommand command) {
validate(command);
Coupon coupon = couponService.find(command.getCouponId());
Money amount = priceCalculator.calculate(command, coupon);
Order order = orderRepository.save(command, amount);
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
return OrderResult.success(order.getId());
}
AI 可能很快生成以下测试:
1. 有效商品和优惠券,创建成功。
2. command 为空,抛出异常。
3. couponService 返回优惠券。
4. orderRepository 被调用一次。
5. eventPublisher 被调用一次。
这些测试能够覆盖主流程和部分分支。
但它们可能遗漏:
- 优惠券已经过期。
- 优惠券不属于当前用户。
- 商品价格在查询后发生变化。
- 库存不足但订单已经写入。
- 数据库写入成功,事件发布失败。
- 同一个请求重复提交。
- 当前用户没有创建订单权限。
- 订单创建成功但返回响应时连接断开。
覆盖率解决的是“代码是否被执行过”。
它不一定解决“系统是否在危险情况下表现正确”。
所以我会把测试目标拆成三层:
| 执行覆盖 | 这段代码是否被测试走到? |
| 行为覆盖 | 正常和异常行为是否符合预期? |
| 风险覆盖 | 关键边界和线上风险是否被证明? |
真正有价值的单测,至少要达到第二层;涉及核心业务时,还要尽量覆盖第三层。
二、先从需求和风险生成测试场景
不要一上来让 AI 读取方法并生成测试代码。
第一步应该是让它先设计测试场景:
请先不要生成测试代码。
请根据以下需求和验收标准,设计测试场景:
需求:
用户可以使用优惠券创建订单。
规则:
– 只能使用当前用户拥有的优惠券。
– 已过期优惠券不能使用。
– 已使用优惠券不能重复使用。
– 库存不足时不能创建订单。
– 订单创建失败时不能产生已扣库存状态。
– 同一个幂等键重复提交,最终只能创建一个订单。
请按以下类别输出:
1. 正常流程。
2. 参数边界。
3. 业务状态。
4. 权限安全。
5. 依赖失败。
6. 并发和幂等。
7. 数据一致性。
每个场景包含:前置条件、操作、预期结果、需要验证的副作用。
这一步的输出应该是一张场景表,而不是测试代码。
示例:
| 正常 | 有效优惠券和充足库存 | 创建成功 | 订单和事件各一次 |
| 状态 | 优惠券已过期 | 创建失败 | 不写订单、不扣库存 |
| 权限 | 使用他人优惠券 | 拒绝请求 | 不泄露优惠券信息 |
| 依赖 | 库存服务超时 | 返回可识别错误 | 不产生部分成功 |
| 幂等 | 相同幂等键并发提交 | 只有一个订单 | 数据库只有一条记录 |
三、边界不是“传个 null”这么简单
AI 生成测试时,最容易把边界理解成空值和负数。
但业务边界通常还包括状态、时序、数据量和系统依赖。
1. 输入边界
- null、空字符串和空集合。
- 最小值、最大值和超长值。
- 非法格式和特殊字符。
- 重复元素和数量过大。
2. 状态边界
- 未创建、处理中、已完成、已取消。
- 已过期、已使用、已删除。
- 当前用户拥有和不拥有。
- 资源存在和不存在。
3. 时间边界
- 刚好到期。
- 到期前一秒和到期后一秒。
- 跨时区和跨天。
- 重试窗口和超时窗口。
4. 依赖边界
- 返回空结果。
- 返回错误码。
- 超时。
- 部分成功。
- 重复响应。
- 依赖版本或配置不一致。
5. 并发边界
- 两个相同请求同时到达。
- 第一次请求成功但响应丢失。
- 消息被重复消费。
- 数据在查询和写入之间被其他请求修改。
可以让 AI 对已有测试做一次“边界反查”:
请不要根据测试数量评价质量。
请反向检查当前测试集还缺少哪些边界:
1. 输入边界。
2. 状态边界。
3. 时间边界。
4. 依赖失败边界。
5. 并发和幂等边界。
每个遗漏场景请说明:
– 为什么它重要。
– 可能造成什么后果。
– 应该观察什么结果。
四、让 AI 写测试前,先告诉它“应该观察什么”
测试不是为了让方法执行一次,而是为了证明结果正确。
因此,在生成测试代码前,我会先定义可观察结果。
1. 返回值
断言返回订单号、状态和错误类型符合预期。
2. 数据状态
断言成功时订单被写入,失败时订单没有新增或状态没有错误变化。
3. 依赖调用
断言库存预占成功后才允许写订单。
断言优惠券校验失败时不会调用扣库存。
4. 消息和事件
断言成功只发布一次事件,失败不发布错误事件。
5. 日志和审计
断言安全操作产生审计记录,但不包含完整手机号和令牌。
可以使用下面的 Prompt:
请为下面的测试场景设计“可观察结果”,暂时不要写代码。
请分别说明:
1. 应该断言什么返回值。
2. 应该检查什么数据库状态。
3. 哪些依赖应该被调用或不应该被调用。
4. 是否需要检查事件、缓存、日志或审计。
5. 哪些结果不能只通过 Mock 验证。
这一步可以减少只断言 notNull、只断言方法被调用一次这类低价值测试。
五、AI 生成单测时,最容易出现的 6 个问题
1. 测试和实现绑定得太紧
如果测试大量验证私有方法、内部变量和具体调用顺序,代码一重构,测试就全部失效。
更好的测试优先验证:
- 对外行为。
- 业务结果。
- 重要副作用。
- 失败后的状态。
2. 只验证“调用了”,不验证“调用是否正确”
下面的断言价值有限:
verify(repository).save(any());
还应该确认:
- 保存的是哪个用户的数据。
- 金额和状态是否正确。
- 保存发生在什么条件下。
- 失败时是否根本不应该调用。
3. Mock 返回值过于理想
AI 经常让所有依赖都返回成功结果:
库存成功、支付成功、数据库成功、消息成功。
这只能覆盖最顺利的主流程。
要主动要求它生成:
- 空结果。
- 异常。
- 超时。
- 错误码。
- 重复响应。
- 部分成功。
4. 断言过于宽松
例如:
assertNotNull(result);
这无法证明状态、金额、错误类型和资源状态正确。
断言应该尽量描述业务结果,而不是只证明“返回了一个对象”。
5. 测试数据没有表达业务含义
如果测试中到处都是:
"test", 1, 2, true, null
读者很难知道这些值为什么重要。
建议使用有意义的数据构造:
CreateOrderCommand command = commandOf(
userId("user-42"),
couponId("coupon-expired"),
quantity(2)
);
6. 测试只验证当前实现,没有验证需求
如果实现里错误地把“优惠券不存在”当作“无需优惠”,而测试也按照这个逻辑断言,测试就会把缺陷固定下来。
所以测试场景应该先来自需求和验收标准,再来自代码分支。
六、用一个真实场景设计测试矩阵
假设有一个“修改手机号”服务:
输入:用户 ID、新手机号、旧手机号验证码
规则:
– 只能修改当前用户自己的手机号。
– 验证码必须正确且未过期。
– 新手机号不能被其他用户占用。
– 修改成功后发送安全通知。
– 修改失败时原手机号保持不变。
可以先让 AI 设计矩阵:
| 正常修改 | 验证码正确,新手机号未占用 | 提交修改 | 修改成功 | 发送一次通知 |
| 验证码错误 | 验证码错误 | 提交修改 | 返回验证码错误 | 手机号不变 |
| 验证码过期 | 验证码过期 | 提交修改 | 返回验证码过期 | 手机号不变 |
| 越权修改 | 请求用户不是当前用户 | 提交修改 | 拒绝访问 | 不查询敏感信息 |
| 手机号占用 | 新手机号已被占用 | 提交修改 | 返回业务错误 | 手机号不变 |
| 重复提交 | 相同请求重复提交 | 连续提交 | 行为符合幂等规则 | 通知不重复 |
| 并发修改 | 两个请求修改同一用户 | 并发提交 | 只有一个结果生效 | 数据不覆盖错误 |
| 通知失败 | 手机号已保存,通知服务失败 | 提交修改 | 按约定返回或补偿 | 记录失败状态 |
注意最后一行。
“手机号已保存,但通知失败”到底算成功还是失败,不应该由测试作者自行决定,而应该先确认业务规则。
七、让 AI 先生成测试设计,再生成测试代码
我建议把过程拆成两轮。
第一轮:测试设计
请根据需求、验收标准和现有代码,设计测试矩阵。
要求:
– 暂时不要写测试代码。
– 先覆盖正常、边界、状态、权限、异常、并发和幂等场景。
– 每个场景写清前置条件、操作、预期结果和副作用。
– 标记哪些规则尚未确认。
– 按风险优先级排序。
第二轮:生成测试代码
请根据已经确认的测试矩阵生成单元测试。
要求:
1. 遵循当前项目已有测试风格。
2. 优先验证业务行为和重要副作用。
3. 不要为了覆盖率测试私有实现细节。
4. 对外部依赖明确区分成功、空结果、异常、超时和重复响应。
5. 测试名称要表达业务场景和预期结果。
6. 每个测试只验证一个主要行为。
7. 生成代码后,说明每个测试覆盖了哪条业务规则。
如果 AI 直接生成了测试代码,也可以让它回头生成“测试与规则映射表”,检查是否有重要规则没有对应测试。
八、把线上故障转成回归测试
Day 16 讲过,排障时应该记录:
- 触发条件。
- 实际现象。
- 时间线。
- 关键日志。
- 最终根因。
这些内容可以直接转换为回归测试:
请根据下面的线上故障记录,设计一个最小回归测试。
线上现象:
用户重复点击提交后,创建了两条订单。
已确认根因:
请求在查询幂等记录和插入订单之间存在竞态。
请说明:
1. 测试前置数据。
2. 并发或重复请求的触发方式。
3. 最终必须断言的数据状态。
4. 该测试应该放在单元、集成还是端到端层。
5. 如果单测无法可靠复现,还需要什么更高层测试。
一个好的回归测试,不只是证明“这次 Bug 修好了”,还要尽量保留当时的触发条件。
九、单测、集成测试和端到端测试怎么分工
不是所有边界都适合用单测覆盖。
| 参数校验和纯业务计算 | 单元测试 |
| Service 与 Repository 的事务行为 | 集成测试 |
| 数据库唯一约束和并发写入 | 集成测试或并发测试 |
| 消息发布与消费 | 集成测试 |
| 第三方接口真实协议 | 契约测试或集成测试 |
| 完整用户流程 | 端到端测试 |
可以让 AI 帮你判断测试层级:
请判断下面每个测试场景适合放在哪一层:单元、集成、契约或端到端。
判断依据包括:
1. 是否需要真实数据库约束。
2. 是否需要真实事务边界。
3. 是否需要消息或网络交互。
4. 是否需要验证多个模块协作。
5. 是否需要真实用户入口。
不要为了运行速度把必须依赖真实系统行为的场景强行改成单测。
十、如何判断一组 AI 生成的测试是否有价值
我会从下面几个角度检查:
[ ] 每个关键业务规则都有至少一个测试场景
[ ] 测试不只有成功路径
[ ] 边界覆盖了输入、状态、时间、依赖和并发
[ ] 失败时的数据状态也有断言
[ ] 重要副作用有明确验证
[ ] Mock 没有把真正要验证的行为全部替换掉
[ ] 断言表达业务结果,而不是只断言对象不为空
[ ] 测试数据能看出业务含义
[ ] 测试名称能说明场景和预期
[ ] 线上故障已经转成回归测试
[ ] 需要真实数据库或消息的场景没有被错误地伪装成单测
[ ] 测试失败时能帮助定位问题
最后一项尤其重要。
如果测试失败后只能看到“expected true but was false”,它对排障帮助很小。
十一、我的 AI 单测 Prompt
下面是一份可以直接复用的完整 Prompt:
请作为一名重视业务风险的测试工程师,协助我为下面的代码设计和生成测试。
一、需求和验收标准
<填写业务需求、规则和验收标准>
二、项目上下文
<填写技术栈、测试框架、已有测试风格和依赖约束>
三、待测试代码
<填写代码、调用方、返回值和关键依赖>
四、已知风险
<填写边界、线上故障、权限、并发和数据一致性风险>
请分两阶段工作。
第一阶段:只输出测试设计,不写测试代码。
– 按正常、边界、状态、异常、安全、并发、幂等分类。
– 每个场景包含前置条件、操作、预期结果和副作用。
– 指出需要人工确认的规则。
– 标记适合的测试层级。
第二阶段:等待我确认测试设计后,再生成测试代码。
生成代码时要求:
– 使用当前项目已有测试风格。
– 测试名称表达业务场景。
– 优先验证行为和结果,不测试无价值的内部细节。
– 对成功、空结果、异常、超时和重复响应分别设计测试。
– 对失败场景断言数据没有错误变化。
– 对并发和幂等风险说明单元测试是否足够。
– 每个测试标注覆盖的业务规则。
最后输出测试覆盖矩阵,并列出仍然没有被证明的风险。
十二、总结
AI 可以很快写出大量单测,但测试的价值不在数量,而在于它是否证明了重要行为。
一组高价值测试,应该覆盖:
我会坚持这套节奏:
先从需求识别风险
↓
再设计测试矩阵
↓
明确应该观察什么
↓
让 AI 生成测试代码
↓
执行并审查测试质量
请记住:
覆盖率告诉你代码走过哪些路,好的测试还要告诉你走错路时系统会怎样。
下一篇文章,我们继续进入工程判断:
用 AI 做重构前评估:哪些代码值得改,哪些别碰。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注专栏。 也欢迎在评论区留言:你让 AI 写单测时,最容易遗漏边界、异常,还是并发场景?
✍坚持原创,求关注,点赞,收藏




