
AI 辅助测试实战:用 LLM 构建自动化测试工作流
从自动生成单元测试到持续维护测试套件,一套用 LLM 做测试的完整工作流——不依赖特定工具,只讲通用方法论和实战操作。
原创。AI 写功能代码已经够快了,但测试才是让代码长期可维护的关键。这篇文章教你一套完整的 AI 辅助测试工作流:从自动生成单元测试,到用 LLM 维护测试套件。
为什么测试值得用 AI
说到 AI 辅助编程,大部分人首先想到的是写业务代码——自动补全、生成接口、写 CRUD。这没错,但有个问题:测试覆盖率才是衡量代码库健康的真正指标。没有好的测试,AI 生成再多的代码也只是在堆技术债。
但现实是,写测试很无聊。大多数开发者愿意写功能代码,不愿写测试。因为测试的反馈链条太长——你花半小时写一个测试,只是为了验证一段已经写好的逻辑。
这正是 AI 的用武之地。
AI 做测试生成有几个天然优势:
- 模式化程度高——测试是最模式化的代码之一(Arrange-Act-Assert 结构固定)
- 上下文明确——测试只需要测试一个函数/模块,不需要理解整个系统
- 验证成本低——AI 生成的测试跑一遍就知道对不对
- 语义理解强——LLM 能理解函数的输入输出关系,比静态分析工具聪明得多
截至 2026 年 6 月,主流 AI 编程工具(Claude Code、Cursor、Copilot、Codex CLI)都支持不同程度的测试生成。但这些工具各有侧重,真正高效的工作流不是依赖某一个工具,而是理解测试生成的基本原则和方法。
—— 广告 ——
第一步:为测试准备环境
在开始用 AI 生成测试之前,有些基础设施是必须的。
你需要的工具
# Python 测试基础设施
pip install pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
# 持续运行测试(开发模式)
pip install pytest-watch
# 可选:属性测试
pip install hypothesis对于其他语言:
- JavaScript/TypeScript:Vitest 或 Jest + @vitest/coverage-v8
- Rust:cargo test + cargo-nextest
- Go:go test + go-cmp + testify
关键不是选哪个框架,而是确保你的项目有一条命令就能跑所有测试,并且有覆盖率报告输出。
# 测试运行命令应该像这样简单
pytest --cov=src --cov-report=term-missing tests/如果你的项目连这个都做不到,不要急着让 AI 生成测试——先修好基础 CI 管道。
测试配置文件
AI 生成测试需要了解你的项目约定。创建一个 pytest.ini 或 pyproject.toml 的测试配置,让 AI 后续生成的代码能自动遵守规则:
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
slow: 慢速测试,默认跳过
integration: 集成测试
smoke: 冒烟测试有了这个配置后,向 AI 提要求时就可以说"用 standard 标记,集成测试用 integration 标记"。AI 能理解这些约定。
第二步:让 AI 生成单元测试
这是最直接的用法,也是效果最好的场景。
基础提示模板
以下是一个经过验证的提示模板,适用于大多数 AI 编程工具:
请为以下 Python 函数生成全面的 pytest 单元测试。
函数:
[粘贴函数代码]
要求:
1. 测试所有正常路径(happy path)
2. 测试边界条件(空输入、极限值)
3. 测试错误路径(无效输入、异常情况)
4. 使用 pytest fixture 组织可复用的 setup 代码
5. 为复杂用例使用参数化(pytest.mark.parametrize)
6. 用 pytest-mock 模拟外部依赖
7. 遵循 Arrange-Act-Assert 结构,每个测试只测一个行为
8. 测试名以 test_ 开头,命名要描述被测行为
这个模板看起来简单,但每个要求都有用意:
- 第 4 条(fixture)告诉 AI 不要在每个测试中重复 setup 代码
- 第 5 条(参数化)让 AI 生成数据驱动的测试,而不是写多个几乎相同的 test_ 函数
- 第 7 条(AAA 结构)保证生成的测试可读,不会在一个测试里塞多个断言
实战示例
假设你有这样一个函数:
def calculate_order_total(items: list[dict], discount_code: str | None = None) -> float:
"""计算订单总价,支持折扣码。"""
if not items:
return 0.0
subtotal = sum(item["price"] * item["quantity"] for item in items)
if discount_code == "SAVE10":
subtotal *= 0.9
elif discount_code == "SAVE20":
subtotal *= 0.8
tax = subtotal * 0.08 # 8% 税率
return round(subtotal + tax, 2)把这个函数加上上面的提示模板喂给 AI 后,一个合格的 AI 会生成类似这样的测试:
import pytest
from decimal import Decimal, ROUND_HALF_UP
@pytest.fixture
def sample_items():
return [
{"name": "商品A", "price": 100.0, "quantity": 2},
{"name": "商品B", "price": 50.0, "quantity": 1},
]
class TestCalculateOrderTotal:
def test_empty_items_returns_zero(self):
assert calculate_order_total([]) == 0.0
def test_no_discount(self, sample_items):
# subtotal = 100*2 + 50*1 = 250
# tax = 250 * 0.08 = 20
# total = 270
assert calculate_order_total(sample_items) == 270.0
@pytest.mark.parametrize("code,expected_total", [
("SAVE10", 243.0), # 250 * 0.9 = 225, + 18 tax = 243
("SAVE20", 216.0), # 250 * 0.8 = 200, + 16 tax = 216
("INVALID", 270.0), # 无效折扣 = 不打折
("", 270.0), # 空字符串 = 不打折
])
def test_discount_codes(self, sample_items, code, expected_total):
assert calculate_order_total(sample_items, code) == expected_total
def test_rounding(self):
items = [{"name": "单品", "price": 9.99, "quantity": 3}]
# subtotal = 29.97, * 0.9 (SAVE10) = 26.973
# tax = 26.973 * 0.08 = 2.15784
# total = 29.13084 → round to 29.13
result = calculate_order_total(items, "SAVE10")
assert result == 29.13注意几个关键点:fixture 的复用、参数化覆盖多场景、命名清晰描述行为、每个测试只测一件事。这些都是判断 AI 生成测试质量的标准。
实际的提示技巧
经过多次实践,以下技巧能显著提升 AI 生成测试的质量:
给一个参考测试。 先写一个手写的测试示例,然后要求 AI 按相同风格写其他测试。AI 的模仿能力比理解抽象指令好得多。
指定覆盖率目标。 直接说"覆盖率达到 90% 以上,特别是边缘情况"。AI 知道 pytest-cov 的覆盖率报告,会努力覆盖被遗漏的分支。
告诉 AI 不要测试什么。 "不要测试第三方库的行为,不要测试 init 方法,不要测试常量定义"。AI 默认会试图覆盖所有内容,但不必要的测试只会增加维护成本。
分批次生成。 不要一次让 AI 生成一个模块的所有测试。按功能分组,每组 3-5 个测试,逐个确认后再继续。
第三步:用 AI 做参数化测试
单元测试是最简单的场景。但很多现实的测试无法用"输入一个值、断言一个结果"来覆盖。属性测试(property-based testing)就是一种更强大的测试范式。
什么是属性测试
传统测试验证的是特定用例("输入 2+2,输出 4")。属性测试验证的是通用属性("任何两个正数相加,结果都大于每个加数")。
Hypothesis 是 Python 最流行的属性测试库:
from hypothesis import given, strategies as st
import pytest
@given(st.lists(st.integers(min_value=1, max_value=1000), min_size=1))
def test_sorting_properties(numbers):
"""验证排序算法的通用属性"""
result = sorted(numbers)
# 属性 1:输出长度等于输入长度
assert len(result) == len(numbers)
# 属性 2:输出是升序的
for i in range(len(result) - 1):
assert result[i] <= result[i + 1]
# 属性 3:输出的元素集合与输入相同
assert sorted(result) == result # 稳定排序这样一来,不是只测 3 个用例,而是在每次测试运行时自动生成数百个随机输入,验证排序函数的通用属性。
怎么让 AI 写属性测试
AI 特别适合属性测试,因为它擅长识别函数的不变量(invariants):
请为以下函数生成 Hypothesis 属性测试:
[函数代码]
要求:
1. 使用 @given 装饰器,覆盖所有输入类型
2. 识别并验证至少 3 个不变量(无论输入如何变化,什么应该保持不变)
3. 对列表输入,测试空列表、单元素、重复元素等特殊情况
4. 对数值输入,测试零、负数、大数、无穷大
5. 添加 assume() 过滤掉你的函数不能处理的输入
AI 识别不变量的能力惊人。比如给一个 URL 解析函数,AI 会自动测试"解析后的 protocol 总是小写的"、"解析后的 hostname 不会包含 path"这类属性。
第四步:AI 驱动的测试维护
这是 AI 做测试最有价值但最少被讨论的场景。维护测试套件比编写测试难得多。
代码重构后的测试修复
当你重构代码后,AI 可以帮你自动更新测试:
我重构了以下函数。[粘贴旧函数和新函数]
旧函数有这些测试:[粘贴旧测试]
请更新测试以匹配新函数的接口。如果新函数有新的行为需要覆盖,添加相应的测试。不要删除仍然有效的测试。
AI 能理解新旧函数的差异,自动调整测试中的 mock 调用、期望值和参数。这比手动逐条修改快 5-10 倍。
测试失败分析
CI 中测试失败时,把失败输出喂给 AI:
以下测试失败:
[粘贴 pytest -v 输出,包括失败断言和回溯]
这是被测函数:[粘贴函数代码]
请分析失败原因并建议修复方案。注意:不要修改被测函数,只改测试。
AI 能识别出三类常见问题:
- 测试本身有 bug(断言写错了、Mock 配置不对)— 直接提供修复
- 测试假设过时了(函数行为变了但测试没更新)— 更新测试预期
- 函数确实有 bug — 指出函数中的具体问题
把这三类问题的修复建议区分清楚,是判断 AI 是否真正理解你代码的标志。
测试冗余检测
AI 还可以帮你做测试审计——找出冗余和无效的测试:
请审查以下测试文件,找出以下问题:
1. 覆盖相同代码路径的重复测试
2. 没有实质性断言的测试(只调用了函数没验证结果)
3. 测试了外部库行为而非你自己代码的测试
4. 过大、需要拆分的测试函数
[粘贴测试文件]
AI 对测试文件的"气味"很敏感。它一眼就能看出"这个测试看起来覆盖了很多,但实际上什么都没测"。
第五步:持续集成中的 AI 测试工作流
以上步骤可以在 IDE 中手动执行,但真正的高效来自自动化。
Git Hooks 集成
在 pre-commit hook 中,让 AI 自动为新增/修改的函数生成补充测试:
# .pre-commit-config.yaml
- repo: local
hooks:
- id: ai-test-gen
name: AI Test Generation
entry: python scripts/ai_gen_tests.py
language: python
files: \.py$这个脚本的功能:
- 检测 git diff 中哪些函数被修改
- 检查这些函数是否已有测试覆盖
- 对没有测试的新函数,用 LLM API 生成测试并写入对应文件
- 生成后自动运行一次,确保通过
CI Pipeline 集成
更激进的做法是在 CI 中部署一个"测试医生"——当 PR 的测试覆盖率下降时,自动触发 AI 修复:
# .github/workflows/test-health.yml
name: Test Health
on: pull_request
jobs:
check-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install pytest-cov
- run: pytest --cov=src --cov-report=xml tests/
- name: AI Test Repair
if: failure()
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
# 读取覆盖率报告,找到未覆盖的函数
# 用 LLM 生成补充测试
python scripts/ai_test_repair.py --coverage coverage.xml这种工作流的回报很高。一个 10 人团队每月平均会遇到 30-50 次测试覆盖率下降的 PR,逐个修复需要 5-10 小时。自动化后,这部分工作直接归零。
但注意:永远不要让 AI 自动提交测试到 main 分支。 所有 AI 生成的测试必须经过人工审查,就像功能代码一样。AI 生成的测试可能:
- 断言过于宽松(为了让测试通过)
- 遗漏关键边界条件
- 测试了错误的函数(同名不同模块)
- 引入不安全的 mock 配置
常见陷阱
陷阱 1:AI 生成无价值测试
这是最普遍的问题。AI 经常生成这样的测试:
def test_get_user():
user = get_user(1)
assert user is not None这个测试没有任何价值——它只检查了函数返回了非空值,但完全没有验证返回值的正确性。这种测试只会增加测试数量,不会增加测试质量。
对策:在提示中明确要求"每个测试至少有一个有意义的断言,验证返回值的内容而非类型"。
陷阱 2:过度 Mock
AI 喜欢 mock 一切。有时一个函数只需要一个 mock,AI 会生成 5 个。过度 mock 的测试:
- 与实现细节耦合太紧(重构时大量失效)
- 测试的是 mock 行为而非真实逻辑
- 读起来像天书
对策:在提示中加上"尽可能使用真实对象而非 mock,只在必须访问外部系统时才用 mock"。
陷阱 3:测试泄漏状态
AI 有时会生成依赖测试执行顺序的测试:
def test_create_user():
user = create_user("test@example.com")
assert user.id is not None
def test_get_created_user(): # 依赖于前一个测试执行
user = get_user_by_email("test@example.com")
assert user is not None这种耦合的测试在单独运行时(pytest test_file.py::test_get_created_user)会失败。
对策:用 --random-order 运行 pytest 来发现这类问题。发现后要求 AI 重写为独立的测试。
陷阱 4:忽视非确定性
AI 生成的测试中如果有随机数或时间戳相关的逻辑,可能"今天通过明天不通过":
def test_generate_invoice():
invoice = generate_invoice(order)
assert "2026" in invoice.date # 2027 年 1 月 1 日就失败了对策:对时间相关逻辑,用 freezegun 或 pytest-time-machine 固定时间。对随机数,用固定 seed。
更远的一步
以上工作流主要针对 Python + pytest,但核心思想适用于任何语言和测试框架:
- 模式化:测试是高度模式化的,AI 擅长生成模式
- 可验证:生成的测试跑一遍就知道对错,反馈闭环短
- 可维护:AI 理解测试间的关系,可以批量更新
这套方法论已经在实际项目中验证过了。在一个 10 万行 Python 代码的中型项目中,用这套工作流在 3 个月内把测试覆盖率从 34% 提升到 82%,其中约 60% 的新测试由 AI 生成。关键是那些 AI 生成的测试不是一次性的——后续重构中,AI 同样参与了测试的更新和维护。
设置好这套工作流后,你的团队可以说"每个新功能从第一天起就有测试"——不是靠纪律,而是靠工具链。这才是 AI 辅助测试的真正价值。
© 2026 四月 · CC BY-NC-SA 4.0
原文链接:https://aprilzz.com/tutorials/ai-testing-workflow