AI 辅助生成单元测试:从一行提示词到可信的测试用例

举报
yd_269451999 发表于 2026/11/06 09:34:12 2026/11/06
【摘要】 写单元测试是最容易被拖延的工作:业务代码写完就想提交,测试总想着“下次补”。最近我在 Java 项目里试着把这部分工作交给 AI,发现它确实能把写测试的时间从“一下午”压缩到“半小时”,但前提是人要把好两道关:说清楚测什么,复核生成的断言。 一、先讲清楚行为,再让 AI 写测试AI 写测试的质量,取决于你给的信息是否完整。直接丢一段代码说“帮我写测试”,得到的往往是一堆只覆盖 happy p...

写单元测试是最容易被拖延的工作:业务代码写完就想提交,测试总想着“下次补”。最近我在 Java 项目里试着把这部分工作交给 AI,发现它确实能把写测试的时间从“一下午”压缩到“半小时”,但前提是人要把好两道关:说清楚测什么,复核生成的断言。

一、先讲清楚行为,再让 AI 写测试

AI 写测试的质量,取决于你给的信息是否完整。直接丢一段代码说“帮我写测试”,得到的往往是一堆只覆盖 happy path 的用例。比较有效的做法,是在提示词里一次讲清四件事:

  • 被测对象和它的职责;
  • 正常的输入输出是什么;
  • 边界条件有哪些,比如空值、零、负数、上限;
  • 异常情况应该抛什么错、返回什么。

以一个订单折扣计算为例。被测类 DiscountCalculator 依赖外部的会员折扣服务 PriceService,规则是:会员折扣率最高按 30% 封顶,金额必须为正数,结果保留两位小数、四舍五入。

把这些规则写进提示词,再指定用 JUnit 5 + Mockito 的风格生成,AI 给出的第一版通常已经像模像样。

二、一个能跑的例子:JUnit 5 + Mockito

下面是被测类的核心逻辑(简化版):

public class DiscountCalculator {
    private final PriceService priceService;

    public DiscountCalculator(PriceService priceService) {
        this.priceService = priceService;
    }

    public BigDecimal calculate(String userId, BigDecimal amount) {
        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("amount must be positive");
        }
        BigDecimal rate = priceService.vipDiscountRate(userId);
        if (rate == null) {
            rate = BigDecimal.ZERO;
        }
        if (rate.compareTo(new BigDecimal("0.3")) > 0) {
            rate = new BigDecimal("0.3");
        }
        return amount.multiply(BigDecimal.ONE.subtract(rate))
                .setScale(2, RoundingMode.HALF_UP);
    }
}

AI 生成的测试类(经人工复核调整后):

@ExtendWith(MockitoExtension.class)
class DiscountCalculatorTest {

    @Mock
    private PriceService priceService;

    private DiscountCalculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new DiscountCalculator(priceService);
    }

    @Test
    void vipUserGetsDiscount() {
        when(priceService.vipDiscountRate("u1")).thenReturn(new BigDecimal("0.2"));
        assertEquals(new BigDecimal("80.00"),
                calculator.calculate("u1", new BigDecimal("100")));
    }

    @Test
    void discountIsCappedAt30Percent() {
        when(priceService.vipDiscountRate("u2")).thenReturn(new BigDecimal("0.5"));
        assertEquals(new BigDecimal("70.00"),
                calculator.calculate("u2", new BigDecimal("100")));
        verify(priceService).vipDiscountRate("u2");
    }

    @Test
    void nullRateMeansNoDiscount() {
        when(priceService.vipDiscountRate("u3")).thenReturn(null);
        assertEquals(new BigDecimal("100.00"),
                calculator.calculate("u3", new BigDecimal("100")));
    }

    @Test
    void nonPositiveAmountThrows() {
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculate("u1", new BigDecimal("0")));
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculate("u1", null));
    }

    @Test
    void resultIsRoundedHalfUp() {
        when(priceService.vipDiscountRate("u1")).thenReturn(new BigDecimal("0.1"));
        assertEquals(new BigDecimal("9.00"),
                calculator.calculate("u1", new BigDecimal("10.005")));
    }
}

这个例子里有几个值得注意的点:正常折扣、封顶、空折扣率、非法入参、四舍五入,五个用例覆盖了主要的分支;外部依赖用 Mockito 的 @Mock 隔离,测试不依赖真实的会员服务;verify 只用来确认关键交互,而不是把每个调用都锁死。

三、AI 生成测试常见的三个坑

第一,只测 happy path。AI 容易顺着代码的“主干”写用例,边界和异常分支经常缺失。补边界清单是人的责任,写提示词时就要列出来。

第二,断言“幻觉”。AI 有时会断言代码实际没有的行为,比如断言了某个不存在的返回值。每一行断言都要能对应到代码或需求,跑测试通过还不够,断言本身也得是真的。

第三,把实现细节焊死在测试里。比如对某个内部方法的调用次数做严格校验,或者断言了临时的日志格式。这类测试会在重构时成片变红,维护成本很高。好的测试应该只关心“行为”,而不是“怎么做”。

四、人工复核的三步检查

我现在的做法是:AI 生成之后,一定要做三件事。第一,跑一遍,确认全部通过,并且是真的在跑——有的测试看起来通过,实际是因为 Mock 配错了,根本没走到被测代码。第二,做一次“变异检查”:故意改一行实现,比如把封顶值从 0.3 改成 0.4,看对应的测试会不会变红;如果不变红,说明测试没有真正覆盖这条分支。第三,删掉没有意义的断言和用例,只保留能描述业务行为的测试。

五、把流程沉淀成习惯

用熟练之后,AI 生成测试可以变成固定流程:先写清楚业务规则和边界,再让 AI 按 JUnit 5 + Mockito 的风格生成骨架,最后人工复核断言和边界。华为云的 MaaS 能力可以为这类代码助手提供模型支持,在 AI DevSpace 里也能直接用对话的方式让 AI 阅读代码、生成并运行测试,省掉了来回复制粘贴的步骤。对团队来说,比工具更重要的是约定:新功能合并之前,测试用例和实现代码一起评审,AI 生成的测试也一样。

结语

AI 写单元测试不是“不用写测试了”,而是把人力从重复的骨架代码里解放出来,放到更值得花时间的地方:想清楚边界、校验断言、守住行为。测试的价值从来不在行数,而在它能不能在你改代码的那一刻站出来说“不对”。这一关,AI 替不了人,但人可以借 AI 把它做得更好。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。