name: pytest-recording description: 使用pytest-recording(VCR.py)在测试中录制和回放HTTP交互。适用于编写VCR测试、管理cassette文件、配置VCR选项、过滤敏感数据或调试录制的HTTP响应。
pytest-recording(VCR.py)测试
概述
pytest-recording封装了VCR.py,将HTTP交互录制为YAML格式的cassette文件,从而实现无需实时API调用的确定性测试。
快速参考
运行测试
# 运行所有测试(使用现有cassette文件)
uv run pytest tests/
# 运行单个测试
uv run pytest tests/test_module.py::test_function
# 使用新响应重写所有cassette文件
uv run pytest tests/ --vcr-record=rewrite
# 仅录制缺失的cassette文件
uv run pytest tests/ --vcr-record=new_episodes
# 禁用VCR(进行实时请求)
uv run pytest tests/ --disable-recording
录制模式
| 模式 | 标志 | 行为 |
|---|---|---|
none |
--vcr-record=none |
仅回放,若无cassette则失败 |
once |
(默认) | 若无cassette则录制 |
new_episodes |
--vcr-record=new_episodes |
录制新请求,保留现有 |
all |
--vcr-record=all |
总是录制,覆盖现有 |
rewrite |
--vcr-record=rewrite |
删除并重新录制所有cassette |
编写VCR测试
使用VCR的基本测试:
import pytest
@pytest.mark.vcr()
def test_api_call():
response = my_api_function()
assert response.status_code == 200
自定义cassette名称:
@pytest.mark.vcr("custom_cassette_name.yaml")
def test_with_custom_cassette():
pass
多个cassette:
@pytest.mark.vcr("cassette1.yaml", "cassette2.yaml")
def test_with_multiple_cassettes():
pass
conftest.py中的VCR配置
vcr_config fixture控制VCR行为:
@pytest.fixture(scope="module")
def vcr_config():
return {
# 从录制中过滤敏感头部
"filter_headers": ["authorization", "api-key", "x-api-key"],
# 过滤查询参数
"filter_query_parameters": ["key", "api_key", "token"],
# 按这些标准匹配请求
"match_on": ["method", "scheme", "host", "port", "path", "query"],
# 忽略某些主机(不录制)
"ignore_hosts": ["localhost", "127.0.0.1"],
# 录制模式
"record_mode": "once",
}
过滤敏感数据
对于LLM提供商,过滤认证信息:
@pytest.fixture(scope="module")
def vcr_config():
return {
"filter_headers": [
"authorization", # OpenAI, Anthropic
"api-key", # Azure OpenAI
"x-api-key", # Anthropic
"x-goog-api-key", # Google AI
],
"filter_query_parameters": ["key"],
}
响应处理
使用pytest_recording_configure进行高级处理:
def pytest_recording_configure(config, vcr):
vcr.serializer = "yaml"
vcr.decode_compressed_response = True
# 清理响应头部
def sanitize_response(response):
response['headers']['Set-Cookie'] = 'REDACTED'
return response
vcr.before_record_response = sanitize_response
Cassette文件位置
默认情况下,cassette文件存储在tests/cassettes/中,按测试模块组织:
tests/
├── cassettes/
│ └── test_module/
│ └── test_function.yaml
└── test_module.py
调试
Cassette未找到
如果测试因“找不到cassette”而失败:
- 使用
--vcr-record=once运行以创建缺失的cassette文件 - 检查cassette路径是否与测试位置匹配
- 验证cassette文件是否存在且为有效的YAML
请求不匹配
如果VCR无法匹配请求:
- 检查
vcr_config中的match_on标准 - 比较cassette中的请求详情与实际请求
- 使用
--vcr-record=new_episodes添加缺失的交互
过时的Cassette文件
当API响应发生变化时:
- 删除特定的cassette文件并重新运行测试
- 或使用
--vcr-record=rewrite刷新所有cassette文件
查看Cassette内容
# 查看cassette文件
cat tests/cassettes/test_module/test_function.yaml
# 在cassette文件中搜索特定内容
grep -r "error" tests/cassettes/
添加新的LLM提供商
添加新提供商时:
- 识别认证头部(查阅提供商文档)
- 将头部添加到
vcr_config中的filter_headers - 将任何查询参数认证添加到
filter_query_parameters - 使用
--vcr-record=once测试以创建cassette文件 - 验证cassette文件不包含机密信息
常见提供商认证:
| 提供商 | 需要过滤的头部 |
|---|---|
| OpenAI | authorization |
| Anthropic | x-api-key, authorization |
| Azure OpenAI | api-key |
| Google AI | x-goog-api-key |
| Cohere | authorization |
最佳实践
- 绝不提交机密信息:始终过滤认证头部/参数
- 使用描述性测试名称:Cassette名称源自测试名称
- 保持cassette文件小巧:仅模拟需要测试的内容
- 在PR中审查cassette文件:检查敏感数据泄露
- 定期重新生成:API响应可能随时间变化
- 适当使用作用域:共享fixture使用
scope="module"