在很多研发团队里,单元测试经常处在一个比较尴尬的位置。

大家都知道它重要,但实际开发中经常会遇到这些情况:

  • 业务需求赶进度,单测被放到最后;
  • 只测了正常流程,异常分支没有覆盖;
  • 老代码逻辑复杂,不知道该怎么补测试;
  • Mock 写起来麻烦,开发者不愿意写;
  • 代码改动后,没有测试兜底,回归风险很高。

ChatGPT、Claude、Gemini、DeepSeek 这类 AI 助手,正好可以在这个环节发挥作用。它们不能替代开发者理解业务,但可以帮助我们快速梳理测试场景、生成测试代码初稿、补充边界用例,从而降低写单测的启动成本。

本文以一个订单取消接口为例,分享一套 AI 辅助单元测试生成的实践流程。


一、为什么单元测试适合用 AI 辅助

单元测试通常有比较固定的结构:

  1. 准备输入参数;
  2. Mock 依赖方法;
  3. 调用被测方法;
  4. 校验返回结果;
  5. 校验异常;
  6. 校验依赖方法是否被调用。

这类模式化工作,很适合交给 AI 生成初稿。

比如我们可以让 AI 帮忙:

  • 根据代码列出测试场景;
  • 生成 JUnit 测试代码;
  • 补充 Mockito Mock 逻辑;
  • 检查断言是否完整;
  • 发现遗漏的异常分支;
  • 生成参数化测试用例;
  • 根据测试失败信息分析原因。

但需要注意,AI 生成的单测并不一定正确。它可能会:

  • 假设不存在的方法;
  • 写出与项目版本不兼容的语法;
  • Mock 错对象;
  • 忽略真实业务规则;
  • 只断言“不为空”,没有验证核心逻辑;
  • 为了让测试通过,反而修改业务含义。

所以正确使用方式应该是:让 AI 生成测试草稿,再由开发者根据业务规则和项目规范校验。


二、示例场景:订单取消服务

假设有一个订单取消方法,代码如下:

java

@Servicepublic class OrderService {
    @Autowired    private OrderRepository orderRepository;
    @Autowired    private StockService stockService;
    public void cancelOrder(Long orderId, Long userId) {        if (orderId == null || userId == null) {            throw new BizException("参数不能为空");        }
        Order order = orderRepository.findById(orderId);        if (order == null) {            throw new BizException("订单不存在");        }
        if (!Objects.equals(order.getUserId(), userId)) {            throw new BizException("无权限取消该订单");        }
        if (!"WAIT_PAY".equals(order.getStatus())) {            throw new BizException("当前订单状态不允许取消");        }
        order.setStatus("CANCELED");        orderRepository.update(order);
        stockService.releaseStock(order.getOrderNo());    }}

这个方法逻辑不算复杂,但已经包含多个测试点:

  • 参数为空;
  • 订单不存在;
  • 用户无权限;
  • 订单状态不允许取消;
  • 正常取消订单;
  • 取消后是否更新状态;
  • 是否释放库存;
  • 异常情况下是否不调用库存服务。

如果手动写单测,容易只覆盖正常流程。但 AI 可以帮助我们先列出完整场景。


三、先让 AI 生成测试场景,而不是直接写代码

很多人使用 AI 写单测时,会直接输入:

text

帮我给这段代码写单元测试。

这样 AI 确实能生成代码,但质量不稳定。更好的方式是先让它分析测试场景。

Prompt 示例:

text

你是一名有经验的 Java 后端测试开发工程师,请根据下面的 Service 代码,先不要写测试代码,只输出需要覆盖的单元测试场景。
要求:1. 按正常流程、异常流程、边界条件分类;2. 每个场景说明输入、Mock 行为、预期结果;3. 标注哪些依赖方法应该被调用,哪些不应该被调用;4. 如果有业务规则不确定,请标注“需要确认”。
待测试代码:【粘贴 OrderService 代码】

这样做的好处是,开发者可以先 Review 测试场景,确认没有跑偏,再让 AI 继续生成测试代码。

AI 可能会输出类似场景:

场景 输入 Mock 预期
orderId 为空 orderId=null,userId=1 抛出参数不能为空
userId 为空 orderId=1,userId=null 抛出参数不能为空
订单不存在 orderId=1,userId=1 findById 返回 null 抛出订单不存在
用户不匹配 order.userId=2 findById 返回订单 抛出无权限
状态非 WAIT_PAY status=PAID findById 返回订单 抛出状态不允许
正常取消 status=WAIT_PAY findById 返回订单 更新状态为 CANCELED,并释放库存

这一步非常重要,因为测试代码写得再漂亮,如果测试场景错了,也没有意义。


四、再让 AI 生成 JUnit + Mockito 测试代码

场景确认后,可以继续输入:

text

请根据以上测试场景,使用 JUnit 5 和 Mockito 生成单元测试代码。
要求:1. 使用 @ExtendWith(MockitoExtension.class);2. 使用 @Mock 和 @InjectMocks;3. 异常场景使用 assertThrows;4. 正常场景校验 orderRepository.update 被调用;5. 正常场景校验 stockService.releaseStock 被调用;6. 异常场景校验 update 和 releaseStock 不被调用;7. 不要使用项目中不存在的工具类。

AI 可能生成如下代码:

java

@ExtendWith(MockitoExtension.class)class OrderServiceTest {
    @Mock    private OrderRepository orderRepository;
    @Mock    private StockService stockService;
    @InjectMocks    private OrderService orderService;
    @Test    void cancelOrder_orderIdIsNull_shouldThrowException() {        BizException exception = assertThrows(BizException.class, () -> {            orderService.cancelOrder(null, 1L);        });
        assertEquals("参数不能为空", exception.getMessage());        verify(orderRepository, never()).findById(any());        verify(orderRepository, never()).update(any());        verify(stockService, never()).releaseStock(any());    }
    @Test    void cancelOrder_orderNotFound_shouldThrowException() {        when(orderRepository.findById(1L)).thenReturn(null);
        BizException exception = assertThrows(BizException.class, () -> {            orderService.cancelOrder(1L, 1L);        });
        assertEquals("订单不存在", exception.getMessage());        verify(orderRepository).findById(1L);        verify(orderRepository, never()).update(any());        verify(stockService, never()).releaseStock(any());    }
    @Test    void cancelOrder_userNotMatch_shouldThrowException() {        Order order = new Order();        order.setId(1L);        order.setUserId(2L);        order.setStatus("WAIT_PAY");
        when(orderRepository.findById(1L)).thenReturn(order);
        BizException exception = assertThrows(BizException.class, () -> {            orderService.cancelOrder(1L, 1L);        });
        assertEquals("无权限取消该订单", exception.getMessage());        verify(orderRepository).findById(1L);        verify(orderRepository, never()).update(any());        verify(stockService, never()).releaseStock(any());    }
    @Test    void cancelOrder_statusNotWaitPay_shouldThrowException() {        Order order = new Order();        order.setId(1L);        order.setUserId(1L);        order.setStatus("PAID");
        when(orderRepository.findById(1L)).thenReturn(order);
        BizException exception = assertThrows(BizException.class, () -> {            orderService.cancelOrder(1L, 1L);        });
        assertEquals("当前订单状态不允许取消", exception.getMessage());        verify(orderRepository).findById(1L);        verify(orderRepository, never()).update(any());        verify(stockService, never()).releaseStock(any());    }
    @Test    void cancelOrder_waitPayOrder_shouldCancelAndReleaseStock() {        Order order = new Order();        order.setId(1L);        order.setOrderNo("O202501010001");        order.setUserId(1L);        order.setStatus("WAIT_PAY");
        when(orderRepository.findById(1L)).thenReturn(order);
        orderService.cancelOrder(1L, 1L);
        assertEquals("CANCELED", order.getStatus());        verify(orderRepository).findById(1L);        verify(orderRepository).update(order);        verify(stockService).releaseStock("O202501010001");    }}

这类代码虽然还需要根据项目实际包名、异常类、实体类字段进行调整,但已经节省了不少重复劳动。


五、AI 生成单测后,必须重点检查什么

1. 断言是否真的验证了业务结果

有些 AI 生成的测试只会写:

java

assertNotNull(result);

这种断言价值很低。好的单测应该验证关键业务结果,例如:

  • 状态是否从 WAIT_PAY 变成 CANCELED
  • 是否调用了库存释放;
  • 异常情况下是否没有更新订单;
  • 错误消息或错误码是否符合预期。

2. Mock 是否符合真实调用路径

AI 有时会假设方法名,例如:

java

orderRepository.getById(orderId)

但真实代码中是:

java

orderRepository.findById(orderId)

这类问题需要人工修正。

3. 异常分支是否校验副作用

异常测试不只是校验“抛异常”,还要校验不该发生的动作没有发生:

java

verify(orderRepository, never()).update(any());verify(stockService, never()).releaseStock(any());

这可以防止代码在异常前已经产生错误副作用。

4. 测试是否过度依赖实现细节

有些测试会过度校验内部调用顺序,导致代码稍微重构就大量失败。单测应该关注核心行为,不要把每一行内部实现都锁死。


六、让 AI 帮忙补充边界用例

第一轮单测写完后,可以继续让 AI 检查遗漏:

text

请检查以上单元测试是否还有遗漏场景。重点关注:1. 空值;2. 非法状态;3. 权限问题;4. 外部依赖异常;5. 是否需要事务;6. 是否存在并发或重复取消问题。

AI 可能会提示:

  • order.getUserId() 为空时如何处理;
  • order.getStatus() 为空时是否应该抛异常;
  • stockService.releaseStock 抛异常时订单状态是否已经更新;
  • 重复取消同一订单是否需要幂等;
  • 取消订单是否应该加事务;
  • 状态枚举是否应该使用常量或枚举类。

这些问题有些属于单测,有些属于业务设计或代码 Review,需要开发者进一步判断。

例如库存释放失败时,当前代码是先更新订单状态,再释放库存:

java

orderRepository.update(order);stockService.releaseStock(order.getOrderNo());

如果释放库存失败,订单已经变成取消状态,但库存没有释放,就可能出现数据不一致。AI 可能会建议加事务,但如果 stockService 是远程服务,普通数据库事务也解决不了分布式一致性问题。这时就需要人工结合架构判断,而不是简单照抄建议。


七、多模型在单元测试场景中的使用方式

在单元测试生成方面,不同模型可以这样分工:

模型 适合任务
ChatGPT 快速生成测试场景和 JUnit 代码
Claude 处理较长 Service 代码、复杂业务流程
Gemini 根据需求文档整理测试点
DeepSeek 中文业务语境下分析异常分支和边界条件

如果是关键业务代码,可以采用“两轮 AI”方式:

  1. 第一个模型生成测试场景;
  2. 第二个模型检查测试场景是否遗漏;
  3. 开发者确认后再生成代码;
  4. 本地运行测试并修正。

如果团队使用 KULAAI 这类多模型聚合平台,也可以把同一段代码分别交给 ChatGPT、Claude、Gemini、DeepSeek 对比输出。不同模型关注点不同,交叉检查有助于发现遗漏。


八、推荐的 AI 单测生成流程

比较稳妥的流程是:

  1. 粘贴被测方法和相关 DTO、枚举;
  2. 让 AI 先输出测试场景,不写代码;
  3. 人工确认测试场景;
  4. 指定 JUnit、Mockito、Spring Boot Test 等版本;
  5. 让 AI 生成测试代码;
  6. 本地运行测试;
  7. 修复编译错误和 Mock 问题;
  8. 检查断言是否有效;
  9. 用覆盖率工具查看遗漏分支;
  10. 将最终测试纳入 CI。

这个流程看起来比“直接让 AI 写代码”多了几步,但最终质量会稳定很多。


九、注意事项

使用 AI 生成单元测试时,建议注意以下几点:

  • 不要输入未脱敏的生产数据;
  • 不要让 AI 直接决定业务异常码;
  • 不要为了让测试通过而降低断言质量;
  • 不要只测正常流程;
  • 不要忽略外部依赖异常;
  • 不要把 AI 生成代码不运行就提交;
  • 不要把单测覆盖率当成唯一目标。

单元测试的核心不是追求覆盖率数字,而是让关键业务逻辑在未来修改时有自动化兜底。


十、总结

AI 辅助单元测试最大的价值,是帮助开发者降低“写测试的启动成本”。

它可以帮我们:

  • 快速拆解测试场景;
  • 补充异常分支;
  • 生成测试代码初稿;
  • 提醒副作用校验;
  • 输出边界用例清单。

但最终仍然需要开发者确认:

  • 测试场景是否符合业务;
  • Mock 是否符合真实调用;
  • 断言是否有效;
  • 异常分支是否完整;
  • 测试是否能在本地和 CI 稳定运行。

一句话总结:

AI 可以帮你更快写出单元测试初稿,但测试是否真的可靠,仍然取决于开发者对业务逻辑的理解和验证。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐