共计 2260 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么 Agent 工具调用测试特别难?
在 Agent 系统中测试工具调用,就像教机器人使用各种工具后还要检查它是否用对了——这个过程充满隐藏陷阱:

- 异步地狱:大多数工具调用都是非阻塞的,测试时经常遇到 ” 调用发出去了但结果还没回来 ” 的尴尬
- 状态漂移:工具可能改变系统状态(比如数据库写入),每次测试后都需要清理现场
- 异常黑洞:网络超时、权限拒绝、参数错误 … 需要模拟各种异常情况
- 依赖纠缠:工具往往依赖外部服务,测试时不可能总是连接真实环境
分层测试策略:构建安全网
1. 单元测试:模拟工具接口
单元测试的目标是把工具调用隔离出来单独验证。Python 的 unittest.mock 是我们的主力武器:
from unittest.mock import Mock, patch
from typing import Any
# 定义工具接口示例
def weather_query(city: str) -> dict:
"""模拟天气查询工具"""
pass
class TestAgentTools:
@patch('__main__.weather_query') # 替换真实工具
def test_tool_call(self, mock_query: Mock):
# 配置模拟返回值
mock_query.return_value = {'temp': 25, 'humidity': 60}
# 执行被测试的 agent 逻辑
result = your_agent.handle("今天北京天气怎样?")
# 验证调用参数和返回结果
mock_query.assert_called_once_with("北京")
self.assertIn("25℃", result)
关键技巧:
- 使用
side_effect模拟异常抛出 - 通过
call_args_list检查多次调用的参数序列 - 对异步工具使用
asyncio的测试工具
2. 集成测试:搭建测试框架
集成测试需要部分真实组件参与。推荐架构:
[Test Runner] → [Mock Server] ← [Agent Under Test]
↑
(预设工具响应)
实现示例:
import pytest
from fastapi.testclient import TestClient
@pytest.fixture
def mock_tool_server():
"""启动一个模拟工具服务的 fixture"""
app = FastAPI()
@app.post("/tools/weather")
def mock_weather(city: str):
return {"data": {"temp": 22}} if city == "北京" else {}
with TestClient(app) as client:
yield client
def test_agent_with_mocked_tools(mock_tool_server):
# 配置 agent 使用模拟服务器
agent = YourAgent(tool_endpoint=mock_tool_server.base_url)
# 执行测试
response = agent.query("上海现在多少度?")
# 验证集成效果
assert "没有找到" in response # 因为我们只 mock 了北京天气
3. 端到端测试:真实场景验证
端到端测试要覆盖完整工作流:
- 准备测试数据集(如 100 条典型用户 query)
- 启动包含真实工具连接的 agent 服务
- 自动化执行所有测试用例
- 验证:
- 工具选择是否正确
- 最终响应是否符合预期
- 执行耗时是否在阈值内
性能测试:不只是快慢的问题
工具调用性能测试要关注:
- 冷启动延迟:第一次调用工具的开销
- 并发吞吐量:模拟 10-100 个并发请求
- 长尾延迟:P99/P999 延迟指标
使用 locust 的测试示例:
from locust import HttpUser, task
class ToolLoadTest(HttpUser):
@task
def test_weather_tool(self):
self.client.post("/agent/query",
json={"question": "杭州明天会下雨吗?"})
避坑指南:血泪经验总结
- 竞态条件:
- 使用
threading.Lock保护共享工具状态 -
在测试中明确验证并发安全性
-
超时处理:
# 在工具调用层设置超时 async with async_timeout.timeout(3.0): await tool.call() -
随机失败:
- 给 flakey 测试添加重试机制
- 使用
pytest-rerunfailures插件
最佳实践:让测试可持续
-
目录结构建议:
tests/ ├── unit/ │ ├── test_tool_selector.py │ └── test_weather_tool.py ├── integration/ │ ├── test_agent_flow.py │ └── fixtures/ └── e2e/ ├── test_happy_path.py └── data/ -
CI 流水线配置:
# GitHub Actions 示例 jobs: test: steps: - run: pytest tests/unit/ - run: pytest tests/integration/ - run: pytest tests/e2e/ --reruns 3
思考:如何适配你的系统?
测试策略需要根据 Agent 系统的特点调整:
- 工具调用是同步还是异步?
- 是否需要处理会话状态?
- 工具的错误处理策略是什么?
建议从最重要的工具开始,先建立核心测试用例,再逐步扩展覆盖范围。记住:好的测试不是追求 100% 覆盖率,而是抓住那些真正会影响用户体验的关键路径。
正文完
