AI 代理时代的测试:我如何防止 QA 崩溃
Source: Dev.to
AI 代理在一夜之间改变了我的开发节奏。我一天可以交付的代码量比以前一周还能多,这听起来很棒,直到第一次一个微小的边缘案例导致整个流程崩溃。
在这种速度下,质量保证要么成为竞争优势,要么变成持续的应急演练。我选择了前者,并在 d:\Coding\Company\Ochestrator 中围绕一小套能够随代码量扩展的测试设计技术重构了我的测试方法:
- TDD
- EP‑BVA (Equivalence Partitioning + Boundary Value Analysis)
- Pairwise (Combinatorial Testing)
- State Transition Testing
为什么我需要“测试设计”,而不仅仅是“更多测试”
当代码量增加时,问题不仅仅是 coverage。真正的问题是可能的输入和状态空间的增长速度超过了我的时间。
于是我不再问:
- “我为这个函数写了测试吗?”
- “我是否选择了真正代表故障面(failure surface)的测试用例?”
这种思维方式促使我转向结构化的测试设计技术。
TDD:从第一天起就设计可测试性
原则: TDD(测试驱动开发)颠倒了传统的“先写代码,再测试”工作流。它遵循 Red‑Green‑Refactor 循环:
| 阶段 | 描述 |
|---|---|
| Red | 为新需求编写测试并观察其失败。这确认测试确实在检查某些内容,并且需求尚未被满足。 |
| Green | 编写最少量的代码使测试通过。此阶段避免“过度设计”。 |
| Refactor | 在确保测试仍然通过的前提下清理代码。 |
在 Orchestrator 中:
由于 AI 代理可以快速生成复杂的业务逻辑,我使用 TDD 来确保这些逻辑在设计时就是可测试的。例如,在为我们的 Temporal 工作流实现 RetryPolicy 时,我先为指数退避编写测试用例,然后才写下策略逻辑的第一行代码。
# Simplified TDD Example for Retry Logic
def test_retry_interval_calculation():
policy = ExponentialRetry(base_delay=1.0, max_delay=10.0)
# 1st attempt: 1.0 s
assert policy.get_delay(attempt=1) == 1.0
# 2nd attempt: 2.0 s
assert policy.get_delay(attempt=2) == 2.0
# Capped at 10.0 s
assert policy.get_delay(attempt=10) == 10.0
这迫使我将延迟的计算与重试的执行分离,使系统既模块化又稳健。
EP‑BVA:通过数学选择实现效率
原理
- 等价划分 (Equivalence Partitioning, EP): 将输入域划分为若干组(分区),系统在同一组内的行为应当相同。对每个组选择一个代表值进行测试。
- 边界值分析 (Boundary Value Analysis, BVA): 缺陷常常出现在这些分区的“边缘”。对精确的边界以及稍微在边界内外的值进行测试。
在 Orchestrator 中:
在处理用户上传的文件时,我们有严格的大小限制(例如 1 MB 到 10 MB)。
| 分区 | 描述 |
|---|---|
| 无效 | 10 MB |
BVA 点: 0.99 MB, 1.0 MB, 1.01 MB, 9.99 MB, 10.0 MB, 10.01 MB.
我实际应用的一个关键真实案例是 bcrypt 的 72 字节限制。许多开发者没有意识到 bcrypt 会忽略第 72 字节之后的所有字符。
# apps/backend/tests/test_auth_service.py
def test_password_length_boundaries(self, auth_service):
# Boundary: 72 bytes
p72 = "a" * 72
h72 = auth_service.get_password_hash(p72)
# Just above the boundary: 73 bytes
p73 = p72 + "b"
# Bcrypt will treat p73 the same as p72 if only the first 72 bytes are used
assert auth_service.verify_password(p73, h72) is True
通过关注这些特定点,我将数百个潜在的测试用例压缩到仅 6‑10 个高效的用例。
成对测试:驯服组合爆炸
原则: 大多数缺陷要么由单个输入参数引起,要么由两个参数之间的交互导致。成对测试是一种组合方法,确保每一对输入参数至少被测试一次。它在保持高缺陷检测率的同时,大幅减少测试用例数量。
在 Orchestrator 中:
我们的 AI 推理引擎拥有多个配置轴:
| 轴 | 选项 |
|---|---|
| 执行提供者 | CUDA, CPU, OpenVINO |
| 模型大小 | Small, Medium, Large |
| 量化 | INT8, FP16, FP32 |
| 异步模式 | Enabled, Disabled |
总组合数:(3 \times 3 \times 3 \times 2 = 54) 种情况。
使用成对测试,我们可以在大约 12‑15 例 中覆盖任意两个设置之间的所有交互。
# Using allpairspy to generate the matrix
from allpairspy import AllPairs
parameters = [
["CUDA", "CPU", "OpenVINO"],
["Small", "Medium", "Large"],
["INT8", "FP16", "FP32"],
["Enabled", "Disabled"]
]
for i, combo in enumerate(AllPairs(parameters)):
print(f"Test Case {i}: {combo}")
这使我们能够在每次 PR 时保持对硬件兼容性矩阵的高度信心,而无需运行完整的 54 例套件。
状态转移测试:映射流程的生命周期
原则: 当系统行为取决于其当前状态以及发生的事件时使用此技术。我们绘制 状态转移图 并确保:
- 所有 有效 的转移都可以实现。
- 所有 无效 的转移都被拒绝或得到妥善处理。
在 Orchestrator 中:
考虑一个简化的订单处理工作流,状态为 Created → Approved → Shipped → Delivered。approve、ship、deliver 和 cancel 等事件触发转移。通过枚举每个状态/事件对,我们生成一个简洁的测试矩阵,以验证正常路径流和错误处理(例如,尝试 ship 一个仍处于 Created 状态的订单)。
# Example state‑transition test matrix
states = ["Created", "Approved", "Shipped", "Delivered", "Cancelled"]
events = {
"approve": {"Created": "Approved"},
"ship": {"Approved": "Shipped"},
"deliver": {"Shipped": "Delivered"},
"cancel": {"Created": "Cancelled", "Approved": "Cancelled"}
}
def test_state_transitions():
for event, mapping in events.items():
for src, dst in mapping.items():
assert transition(src, event) == dst
# Verify invalid transitions raise an error
invalid_src = set(states) - set(mapping.keys())
for src in invalid_src:
with pytest.raises(InvalidTransition):
transition(src, event)
通过系统性地覆盖状态空间,我们能够捕获仅在特定操作序列后才会出现的 bug——这是纯单元测试覆盖率常常遗漏的。
状态转移的负面测试
- 系统最终停留在正确的结束状态。
在 Orchestrator 中
KYC(了解你的客户)验证工作流是一个复杂的状态机。用户的文档会经历:
PENDING → UPLOADING → PROCESSING → VERIFIED or REJECTED
我实现了测试,以确保 REJECTED 状态的文档不能在未再次经过 PROCESSING 的情况下直接跳转到 VERIFIED。
# apps/backend/tests/test_integration_kyc_workflow.py
def test_invalid_state_transitions(workflow_engine):
workflow_engine.set_state(ImageStatus.REJECTED)
# This should be blocked by the business logic
with pytest.raises(IllegalStateError):
workflow_engine.transition_to(ImageStatus.VERIFIED)
这对可能尝试“短路”逻辑的 AI 代理尤为关键。通过严格测试状态机,我们确保整个业务流程的完整性。
结论
在 AI 代理时代,代码很便宜。信任却不廉价。
让我 QA 不至于崩溃的不是写更多的测试,而是 采用可扩展的测试设计技术:
- TDD 用于快速反馈和更安全的重构
- EP‑BVA 用于系统化边界情况
- Pairwise 用于抑制组合爆炸
- 状态转移测试 用于验证真实工作流
随着我的代码量持续增长,我预计将继续使用这些测试工具。
