AI 写的代码跑过 326 条用例,我为什么仍不敢合并?

举报
霍格沃兹测试学社 发表于 2026/09/07 11:11:24 2026/09/07
【摘要】 电商结算新功能“跨店优惠按金额分摊”上线在即。AI生成代码+326条测试全通过,覆盖率96%,但暴露同源偏差风险:测试与实现共享错误前提,掩盖分摊公平性缺陷。需以业务不变量(如总额精确、商品限额、顺序无关)为锚,辅以变异测试和黄金样本验证,确保账务零误差。

一个电商结算服务准备上线“跨店优惠按商品金额分摊”。开发用 AI 完成了核心函数,也顺手生成了 326 条单元测试。流水线显示:全部通过,行覆盖率 96%。

评审时我只问了一个问题:把“最后一分钱补给余数最大的商品”改成“补给列表第一件商品”,测试会不会红?

答案是不会。因为代码和用例来自同一段提示词,二者都默认“只要总金额相等就行”,没有验证商家维度的分摊公平性。覆盖率很高,错误却能安静地活下来。

image.png

AI 生成代码改变了测试的第一个风险:同源偏差

过去我们担心开发漏写测试;现在更麻烦的是,AI 可以同时生成实现、测试数据和预期结果。产物看起来完整,却可能共享同一个错误前提。

例如实现用浮点数计算优惠,测试也调用同一个 rounding helper 生成 expected。即使舍入规则错了,实际值与预期值仍会一起错。测试通过只能说明两段代码意见一致,不能说明它们符合财务规则。

image.png

先写业务不变量,再允许 AI 写例子

优惠分摊最重要的不是列 326 组输入,而是把不会随实现变化的规则钉死:

  • 每个商品优惠不得小于 0,也不得超过商品金额;
  • 分摊后的优惠之和必须精确等于订单总优惠;
  • 商品顺序改变,不应改变同一 SKU 的分摊结果;
  • 最后一分钱按明确、可审计的规则分配;
  • 金额计算使用十进制定点语义,不能把二进制浮点误差带入账务。

下面是一段可直接放进项目的独立校验。它不复制分摊算法,只验证最终业务状态:

from decimal import Decimal

CENT = Decimal("0.01")

def assert_allocation_contract(items, total_discount, result):
    by_sku = {row["sku"]: Decimal(str(row["discount"])) for row in result}
    assert set(by_sku) == {item["sku"] for item in items}
    assert sum(by_sku.values(), Decimal("0")) == Decimal(str(total_discount))

    for item in items:
        discount = by_sku[item["sku"]]
        price = Decimal(str(item["price"]))
        assert discount >= Decimal("0")
        assert discount <= price
        assert discount.quantize(CENT) == discount

def assert_permutation_invariant(allocate, items, discount):
    normal = allocate(items, discount)
    reversed_result = allocate(list(reversed(items)), discount)
    left = {x["sku"]: x["discount"] for x in normal}
    right = {x["sku"]: x["discount"] for x in reversed_result}
    assert left == right

这里故意没有断言“第一个商品应该分到 3.27 元”。因为具体结果可能随合法算法调整;不变量负责守住账务底线,少量经过业务确认的黄金样本再负责锁定监管或合同口径。

用变异测试检查:你的测试到底会不会抓错

变异测试会自动对生产代码做小幅破坏,例如把大于号改成大于等于、删除一个条件、把加法换成减法,再运行测试。测试失败,说明这个错误被“杀死”;测试仍绿,说明断言存在盲区。

对这段优惠逻辑,至少应手工设计四类变异:

  1. 把 ROUND_HALF_UP 改成 ROUND_DOWN;
  2. 删除“优惠不得超过商品金额”的上限;
  3. 把余数排序从降序改为升序;
  4. 在零金额商品上继续参与分摊。

可以把工具输出简化成一个团队能读懂的门禁:

CRITICAL_MUTATIONS = {"ROUNDING", "BOUNDARY", "AUTH", "MONEY_SUM"}

def mutation_gate(report: list[dict]) -> None:
    survived = [m for m in report
                if m["status"] == "SURVIVED"
                and m["category"] in CRITICAL_MUTATIONS]
    if survived:
        detail = ", ".join(m["location"] for m in survived[:5])
        raise AssertionError(f"关键业务变异仍存活:{detail}")

image.png

326 条测试应该怎样重组

我更愿意看到这样的报告:18 条财务不变量全部通过;12 个经过业务确认的黄金样本通过;金额模块 47 个关键变异全部被杀死;PostgreSQL 真库下的并发与精度测试通过;AI 生成的其余样本作为探索补充。

这时 326 才不是一个装饰数字。

AI 让写代码和补测试都变快了,但“生成得多”不能替代“证据独立”。未来测试工程师最值钱的工作,不是继续与 AI 比谁写的用例多,而是设计一套让错误无处藏身的验收机制:先定义不可妥协的业务性质,再用独立预言机、变异和真实依赖去反证。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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