集成测试用于验证 Agent 能否与模型 API 和外部服务正确协作。不同于使用伪实现和模拟对象的单元测试,集成测试会进行真实的网络调用,以确认组件能够协同工作、凭证有效,并且延迟表现可接受。
由于 LLM 响应具有非确定性,集成测试需要采用不同于传统软件测试的策略。本指南介绍如何为 Agent 组织、编写和运行集成测试。有关为 LangChain 本身贡献代码时使用的通用测试基础设施,请参阅贡献代码。
分离单元测试和集成测试
集成测试速度较慢,并且需要 API 凭证,因此请将它们与单元测试分开。这样可以让你在每次变更时运行快速的单元测试,并将集成测试留给 CI 或部署前检查。
使用 pytest markers 标记集成测试:
import pytest
@pytest.mark.integration
def test_agent_with_real_model():
agent = create_agent("claude-sonnet-4-6", tools=[get_weather])
result = agent.invoke({
"messages": [HumanMessage(content="What's the weather in SF?")]
})
assert len(result["messages"]) > 1
配置 pytest 识别该 marker,并从默认运行中排除集成测试:
[pytest]
markers =
integration: tests that call real LLM APIs
addopts = -m "not integration"
显式运行集成测试:
管理 API 密钥
集成测试需要真实 API 凭证。请从环境变量加载凭证,避免将密钥提交到源代码管理系统。
使用 conftest.py fixture 验证所需密钥是否可用:
import os
import pytest
@pytest.fixture(autouse=True)
def check_api_keys():
if not os.environ.get("OPENAI_API_KEY"):
pytest.skip("OPENAI_API_KEY not set")
对于本地开发,请将密钥存储在 .env 文件中,并使用 python-dotenv 加载:
from dotenv import load_dotenv
load_dotenv()
将 .env 添加到 .gitignore,避免提交凭证。在 CI 中,通过提供商的密钥管理机制注入 secrets,例如 GitHub Actions secrets。
断言结构,而不是内容
LLM 响应在不同运行之间会变化。请不要断言精确的输出字符串,而是验证响应的结构属性:消息类型、工具调用名称、参数形状和消息数量。
def test_agent_calls_weather_tool():
agent = create_agent("claude-sonnet-4-6", tools=[get_weather])
result = agent.invoke({
"messages": [HumanMessage(content="What's the weather in SF?")]
})
messages = result["messages"]
tool_calls = [
tc
for msg in messages
if hasattr(msg, "tool_calls")
for tc in (msg.tool_calls or [])
]
assert any(tc["name"] == "get_weather" for tc in tool_calls)
assert isinstance(messages[-1], AIMessage)
assert len(messages[-1].content) > 0
如需更严格的执行轨迹断言,请使用 AgentEvals 评估器。它支持 unordered 和 superset 等模糊匹配模式。
降低成本和延迟
调用 LLM API 的集成测试会产生真实成本。以下做法有助于让测试套件保持快速且成本可控:
- 使用较小的模型:对于只需要验证工具调用和响应结构的测试,使用
gemini-3.1-flash-lite 或等效模型。
- 设置
maxTokens:限制响应长度,避免生成较长且昂贵的补全。
- 限制测试范围:每个测试只测试一种行为。如果单轮测试足够,请避免串联多次 LLM 调用的端到端场景。
- 选择性运行:使用上文中的测试分离方法,只在 CI 或部署前运行集成测试,而不是在每次保存文件时运行。
agent = create_agent(
"gemini-3.1-flash-lite",
tools=[get_weather],
model_kwargs={"max_tokens": 256},
)
记录和重放 HTTP 调用
对于在 CI 中频繁运行的测试,你可以在首次运行时记录 HTTP 交互,并在后续运行中重放它们,而无需进行真实 API 调用。这样可以在初始记录之后消除成本和延迟。
vcrpy 会将 HTTP 请求/响应对记录到 YAML “cassette” 文件中。pytest-recording 插件会将此能力集成到 pytest 中。
设置 conftest.py,从 cassette 中过滤敏感信息:
import pytest
@pytest.fixture(scope="session")
def vcr_config():
return {
"filter_headers": [
("authorization", "XXXX"),
("x-api-key", "XXXX"),
],
"filter_query_parameters": [
("api_key", "XXXX"),
("key", "XXXX"),
],
}
配置项目识别 vcr marker:
[pytest]
markers =
vcr: record/replay HTTP via VCR
addopts = --record-mode=once
--record-mode=once 选项会在首次运行时记录 HTTP 交互,并在后续运行中重放这些交互。
使用 vcr marker 装饰测试:
@pytest.mark.vcr()
def test_agent_trajectory():
agent = create_agent("claude-sonnet-4-6", tools=[get_weather])
result = agent.invoke({
"messages": [HumanMessage(content="What's the weather in SF?")]
})
assert any(
tc["name"] == "get_weather"
for msg in result["messages"]
if hasattr(msg, "tool_calls")
for tc in (msg.tool_calls or [])
)
首次运行会进行真实网络调用,并在 tests/cassettes/ 中生成 cassette 文件。后续运行会重放已记录的响应。
当你修改提示词、添加新工具或更改预期执行轨迹时,已保存的 cassette 会过期,现有测试会失败。删除对应的 cassette 文件并重新运行测试,以记录新的交互。
后续步骤
了解如何在 Evals 中使用确定性匹配或 LLM-as-judge 评估器评估 Agent 执行轨迹。