共计 3199 个字符,预计需要花费 8 分钟才能阅读完成。
为什么 AI Agent 测试更难?
刚接触 AI Agent 测试时,最容易犯的错误就是套用传统软件的测试方法。实际上,两者的核心差异体现在三个方面:

- 非确定性输出:同样的输入可能产生不同回复(如闲聊场景),传统断言方式会频繁失败
- 上下文依赖:单轮对话测试有效,放到多轮对话中就崩溃
- NLU 的模糊性:用户说 ” 我想订机票 ” 和 ” 要买张去北京的票 ” 本质是同一意图,但字面匹配会误判
测试框架选型实战
在 Python 生态中,最常用的两个测试框架是 Behave 和 PyTest。我们做个快速对比:
- Behave的优势:
- 天然适合用 Gherkin 语法描述对话场景(Given-When-Then)
- 内置步骤复用机制,适合多轮对话测试
-
但断言逻辑需要自己封装
-
PyTest的优势:
- 更灵活的 fixture 机制(模拟用户会话超方便)
- 丰富的插件生态(如 pytest-repeat 处理 flaky test)
- 需要自己实现对话流描述
建议新手从 PyTest 开始,下面用具体代码演示。
最小化测试示例(含完整代码)
先安装必要依赖:
pip install pytest pytest-mock
测试场景:验证机票预订场景的意图识别和槽位填充。新建test_booking_agent.py:
import pytest
from datetime import datetime
class MockAgent:
"""模拟 AI Agent 的核心响应逻辑"""
def __init__(self):
self.context = {}
def respond(self, utterance: str) -> str:
# 简易的意图识别逻辑(实际项目用 Rasa/LUIS 等)if "机票" in utterance or "航班" in utterance:
self.context["intent"] = "book_flight"
# 简陋的槽位提取(生产环境用专业 NLU)if "北京" in utterance:
self.context["destination"] = "北京"
return "请问您的出发日期是?"
elif self.context.get("intent") == "book_flight" and "明天" in utterance:
self.context["date"] = datetime.now().strftime("%Y-%m-%d")
return f"已为您预订 {self.context['destination']} 的机票,日期{self.context['date']}"
return "我没听懂,请重新说明"
# 以下是真正的测试代码
@pytest.fixture
def agent():
"""每个测试用例都会获得全新的 agent 实例"""
return MockAgent()
def test_single_turn_booking(agent):
"""测试单轮机票预订意图识别"""
response = agent.respond("我想订去北京的机票")
assert "出发日期" in response # 模糊匹配更稳定
assert agent.context["intent"] == "book_flight"
assert agent.context["destination"] == "北京"
@pytest.mark.parametrize("input_text,expected_keyword", [("买张飞上海的航班", "出发日期"),
("我要预定机票", "出发日期"),
])
def test_intent_detection(agent, input_text, expected_keyword):
"""参数化测试不同表达方式的相同意图"""
assert expected_keyword in agent.respond(input_text)
def test_multi_turn_booking(agent):
"""测试多轮对话状态保持"""
# 第一轮
response = agent.respond("明天去北京的航班")
assert "出发日期" in response
# 第二轮(依赖上下文)response = agent.respond("不对,我要后天的")
assert "北京" in response
assert "202" in response # 验证年份存在即可
关键设计点说明:
- 模糊断言 :用
"出发日期" in response替代精确字符串匹配 - 上下文隔离:每个测试用例通过 fixture 获取全新 agent 实例
- 参数化测试 :用
@pytest.mark.parametrize覆盖多种输入变体
五大避坑经验
1. 处理 Flaky Test 的三板斧
-
重试机制 :用
pytest-rerunfailures插件自动重试失败用例pip install pytest-rerunfailures pytest --reruns 3 --reruns-delay 1 -
模糊匹配:
# 坏味道:assert response == "请问您的出发日期是?" # 好做法:assert any(keyword in response for keyword in ["日期", "时间", "什么时候"]) -
容忍列表:对非关键字段(如问候语中的随机 emoji)设置允许的内容列表
2. 意图测试数据隔离
- 训练集和测试集必须严格分开(常见错误:用训练数据做测试)
- 建议目录结构:
tests/ ├── intents/ │ ├── booking/ │ │ ├── train_samples.txt # 训练数据 │ │ └── test_samples.txt # 测试数据 │ └── weather/ ├── fixtures/ └── conftest.py
3. 对话状态机测试要点
- 为每个对话路径绘制状态转移图
- 重点测试:
- 正常路径(Happy Path)
- 异常分支(如用户突然改变意图)
- 超时恢复(长时间不响应后重新激活)
4. 压力测试设计
使用 locust 模拟并发用户:
from locust import HttpUser, task
class AgentUser(HttpUser):
@task
def book_flight(self):
self.client.post("/chat", json={
"utterance": "上海到纽约的机票",
"session_id": self.session_id
})
关键指标:
– 90% 请求响应时间 < 2 秒
– 错误率 < 1%
– 会话保持成功率 > 99%
5. 监控指标埋点
在 Agent 代码中添加监控:
from prometheus_client import Counter, Histogram
INTENT_ERRORS = Counter(
'agent_intent_errors_total',
'Intent classification errors by type',
['intent_name']
)
RESPONSE_TIME = Histogram(
'agent_response_time_seconds',
'Response latency distribution',
buckets=[0.1, 0.3, 1, 3, 5]
)
@RESPONSE_TIME.time()
def respond(self, utterance):
try:
# 处理逻辑...
except Exception as e:
INTENT_ERRORS.labels(intent_name="book_flight").inc()
进阶路线图
当基本测试覆盖完成后,可以逐步实施:
- 影子测试:将线上流量复制到测试环境运行
- 语义相似度评估:用 Sentence-BERT 等模型量化回复质量
- 对抗测试:故意输入错别字、无关问题测试鲁棒性
- 可视化测试报告:用 Allure 生成包含对话流程图的可视化报告
测试 AI Agent 就像教小朋友说话——需要耐心、多样的测试用例,以及接受它们偶尔会犯可爱的错误。关键是建立快速验证的闭环,而不是追求 100% 的完美通过率。
正文完
