起因:3000行Service类,0个单元测试

接手这个项目的时候我愣了一下——一个处理订单状态流转的OrderStateService,3000多行,没有一行测试代码。问了一圈才知道,之前的同事都嫌写测试太费劲,Mock依赖服务搞得人头大。CI/CD流水线全靠运气过。

我心想AI不是最擅长生成重复代码吗,单元测试这种套路化的东西,让它代劳是不是刚好?于是开始了三个月的"AI辅助写测试"实验。

结果算不上翻车,但也绝不是"AI一键生成完美测试"那种童话。

Mockito的argThat:AI生成的断言永远不对

第一个坑出在参数匹配上。OrderStateService有个方法会调用外部库存服务检查库存,AI给我生成的测试长这样:

@Test
public void shouldCancelOrderWhenStockInsufficient() {
    InventoryResponse mockResp = new InventoryResponse();
    mockResp.setAvailable(0);
    
    when(inventoryService.checkStock(any(StockCheckRequest.class)))
        .thenReturn(mockResp);
    
    OrderResult result = orderStateService.processOrder(testOrder);
    
    assertEquals(OrderStatus.CANCELLED, result.getStatus());
    verify(inventoryService).checkStock(any(StockCheckRequest.class));
}

跑起来居然绿了。但我总觉得不对劲,这个测试太宽松了——any(StockCheckRequest.class)匹配任何参数,根本验证不了我们传给库存服务的参数是否正确。万一有人把skuIdwarehouseId搞反了,测试照样通过。

我就让AI改成用argThat做精确匹配:

verify(inventoryService).checkStock(argThat(req ->
    "SKU-8848".equals(req.getSkuId()) && "WH-SH-01".equals(req.getWarehouseId())
));

结果AI生成的这行代码直接编译不过。原因是argThat返回的是null需要显式类型转换,而AI完全不理解这个坑——它在训练数据里见过argThat的用法,但不了解Java泛型在Mockito里的类型推断限制。

修复后的版本:

verify(inventoryService).checkStock(
    ArgumentMatchers.<StockCheckRequest>argThat(req ->
        "SKU-8848".equals(req.getSkuId()) && "WH-SH-01".equals(req.getWarehouseId())
    )
);

这个坑翻完我的感受是:AI对Mockito基础用法掌握得不错,一碰到泛型相关的边界case就容易露馅。你自己得懂这些细节才能发现AI写的问题,不然就是"测试全绿但什么都没测到"。

@SpyBean和@MockBean混用的隐蔽bug

项目里有个PaymentService内部调了自己的另一个方法,AI建议用@SpyBean做部分mock:

@SpyBean
private PaymentService paymentService;

@Test
public void testRefundFlow() {
    doReturn(true).when(paymentService).validateRefund(any(RefundRequest.class));
    // ...
}

这个用法的隐患是:@SpyBean会创建真实bean的代理,Spring上下文里的其他bean注入到PaymentService的引用可能跟spy对象不是同一个实例。结果verify阶段拿到的是原始bean而不是spy,verify一直失败,但又不报错——因为Mockito的宽松模式默认不抛异常。

这问题排查了两个多小时。AI给的方案思路是对的(用SpyBean处理内部方法调用),但Spring Test框架里SpyBean的代理机制它完全不理解。

我的经验是:涉及Spring容器和测试框架结合的复杂场景,别让AI直接给方案,自己先把架构理清,再让AI帮你生成具体的mock代码片段。

AI真正帮上忙的地方

说完了坑,讲讲AI在写测试上确实帮到了我的地方。

基础脚手架代码。一个类的测试框架——@ExtendWith、@MockBean声明、setUp方法里的通用mock setup——AI几秒钟就能搞定。这部分工作纯粹是体力活,以前花半小时写,现在半分钟。

@ExtendWith(MockitoExtension.class)
class OrderStateServiceTest {
    
    @Mock
    private InventoryService inventoryService;
    @Mock
    private PaymentService paymentService;
    @Mock
    private NotificationService notificationService;
    
    @InjectMocks
    private OrderStateService orderStateService;
    
    private OrderDO testOrder;
    
    @BeforeEach
    void setUp() {
        testOrder = OrderDO.builder()
            .orderId("ORD-20260610-001")
            .skuId("SKU-8848")
            .quantity(2)
            .build();
    }
    // AI生成到这就停了,具体测试逻辑自己写
}

边界条件补全。我把一个写好的正常路径测试丢给AI,让它列出"还有哪些边界条件没覆盖"。它能在几秒内给出一个清单:空值、负数quantity、并发重复提交、超时未支付、部分发货——这些我自己想也能想到,但AI不漏,这就比人强。

测试数据构造。复杂嵌套对象的Builder链式调用,AI写得很漂亮。以前构造一个带5层嵌套的DTO要写几十行setter,现在描述一下结构AI就给你生成好了。

三个月下来我的做法

现在我的工作流是这样:先自己设计测试策略——哪些路径必须覆盖、哪些mock行为是关键验证点。然后让AI生成脚手架和测试数据,自己写核心的断言和verify逻辑。最后把测试丢给AI做review,让它补充边界条件。

用AI写测试,核心逻辑必须自己把控。mock行为的正确性、断言的严谨程度、测试的独立性——这些才是决定测试质量的东西。AI能做的是加速你的执行,但设计思路和审查责任是程序员的。

别做那种"AI生成完直接commit"的事,我见过同事这么干,code review的时候发现测试里所有的mock都是any(),所有断言都是assertNotNull——这种测试除了拉低覆盖率什么用都没有。


以上就是我在实际项目里用AI辅助写Java单元测试的真实经历。这不是一篇"AI改变测试方式"的宣传文,而是记录了什么地方AI好用、什么地方必须靠人脑的实际体验。如果你也在用AI写测试,希望这些踩坑经历能帮你少走点弯路。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐