AI 单元测试生成:让大模型补齐测试覆盖

当测试覆盖率成为发布门槛,手写用例却总被排期挤掉。大模型(LLM)可以根据函数签名、实现与接口文档反向生成单元测试,把「补覆盖」变成几轮对话。但 AI 生成的测试良莠不齐:它擅长铺量,却常写出看似通过、实则无效(恒真)的断言。本文讲清生成思路、断言质量与人工复核清单,帮你把 AI 当成「初稿生成器」而非「可信作者」。

从函数到用例的生成思路

给模型一个明确的最小上下文,生成质量会明显提升:

  • 给出被测函数源码与依赖签名,而非只给函数名。模型看不到实现时容易臆测行为。
  • 说明「被测意图」:这个函数对非法输入应该抛错还是返回默认值?这决定用例的断言方向。
  • 指定测试框架与命名风格:明确要 pytest 还是 Jest,并给出既有的用例范式,让生成结果风格统一。
  • 分而治之:先让模型列出「应该覆盖哪些场景」,你确认清单后,再逐条生成代码,避免一次产出大段难审的测试。

一个实用的提示结构:函数源码 + 输入约束 + 期望行为描述 + 目标框架 + 输出纯代码。

覆盖边界与异常路径

AI 最容易漏掉的是边界与异常,而这恰是测试价值最高的部分。务必要求模型覆盖:

  • 边界值:空集合、零、最大值、越界下标、超长字符串。
  • 异常路径:非法类型、空值、除零、超时、文件不存在等应触发错误的分支。
  • 不变式:如「排序后长度不变」「金额非负」。这类断言比单点相等更稳健。

可以显式要求:「除正常用例外,额外给出 3 个边界用例与 2 个异常用例」,把覆盖率短板补上。

断言质量:拒绝恒真断言

断言是测试的灵魂。AI 常见失误是写出「恒真断言」:用例永远通过,缺陷被悄悄掩盖。

# 被测函数
def divide(a, b):
    if b == 0:
        raise ValueError("denominator must not be zero")
    return a / b

# AI 生成的 pytest 用例(正确示范)
import pytest
from mod import divide

def test_divide_normal():
    assert divide(6, 3) == 2

def test_divide_by_zero():
    with pytest.raises(ValueError):
        divide(1, 0)

# 恒真断言反例:永远为真,缺陷被掩盖
def test_trivial():
    assert divide(4, 2) is not None   # 恒真,未校验计算结果
// 被测函数
function formatPrice(value) {
  if (typeof value !== "number") throw new TypeError("must be number");
  return "¥" + value.toFixed(2);
}

// AI 生成的 Jest 用例(正确示范)
test("正常格式化", () => {
  expect(formatPrice(9.9)).toBe("¥9.90");
});

test("非数字抛错", () => {
  expect(() => formatPrice("x")).toThrow(TypeError);
});

// 恒真断言反例
test("恒真反例", () => {
  expect(formatPrice(1)).toBeDefined(); // 恒真,未校验金额格式
});

判定标准:删掉某条断言后,若函数存在对应缺陷,用例仍能通过,则这条断言无效,应要求模型重写。

框架适配:pytest 与 Jest

  • pytest(Python):用 assert 原生断言,pytest.raises 包裹异常路径,参数化用 @pytest.mark.parametrize 收敛相似用例。插件生态成熟,可与覆盖率工具直接联动(已核验)。
  • Jest(JavaScript/TypeScript):用 expect(...).toBe/toEqual/toThrow 风格,异常用 expect(fn).toThrow;describe/test 组织层级。与前端工程链集成度高(已核验)。

让模型按你的框架输出,能显著减少后续改写。若项目有自定义 fixtures 或 mock 规范,一并提供给模型。

与覆盖率工具配合

AI 生成只是第一步,覆盖率工具负责量化与查漏:

  • Python 侧常用 pytest 配合 coverage.py 或 pytest-cov,生成行覆盖与分支覆盖报告(工具真实性已核验;具体版本与命令参数待核实)。
  • JS 侧常用 Jest 内置覆盖率或 Istanbul 系工具(具体选型待核实)。
  • 工作流:先跑覆盖率找出未覆盖行,把「未覆盖行对应的函数」丢给模型补用例,再回归运行,形成闭环。

注意:高覆盖率不等于高质量。覆盖率只说明「执行到」,断言质量才说明「验证到」。

局限与人工复核清单

AI 生成测试的已知短板:

  • 测试脆弱:过度依赖实现细节(如文案、私有字段),重构即红。
  • 误判意图:模型按字面推断行为,可能把 Bug 当成预期并据此写断言。
  • 幻觉 API:引用了项目里并不存在的辅助函数或 mock。

人工复核清单(每条都要过):

  • 断言是否非恒真,且真正验证行为而非仅判断「不为空」?
  • 异常用例是否真的走到了预期分支?
  • 是否引入了不存在的依赖或臆造的接口?
  • 用例是否稳定可重复(无随机、无时序依赖)?
  • 覆盖率提升是否来自有效断言,而非凑数?

小结

AI 单元测试生成擅长把「补覆盖」从体力活变成对话,但价值取决于断言质量而非用例数量。给它足够上下文、强制覆盖边界与异常、用覆盖率工具闭环、再用人工复核清单筛掉恒真断言与幻觉依赖,才能让大模型真正成为可靠的测试初稿生成器。记住:AI 写的是草稿,可信结论来自你的一遍复核。

参考与延伸阅读

  • pytest 官方文档(断言、参数化、raises):已核验
  • Jest 官方文档(expect、toThrow、覆盖率):已核验
  • coverage.py 与 pytest-cov 项目主页(Python 覆盖率):已核验
  • 各覆盖率工具的具体版本与命令参数:待核实
  • 团队既有测试规范与 mock 约定:以内部文档为准