pytest-vcr 默认不自动生成 cassette 文件,因其采用「严格模式」:仅读取已有录制文件,请求无匹配时直接报错 vcrfailureerror,而非自动创建;需显式设置 record_mode(如 "once" 或 "all")并确保路径可写、父目录存在。

pytest-vcr 为什么默认不自动生成 cassette 文件?
因为 pytest-vcr 默认行为是「严格模式」:它只读取已存在的 .yml 或 .json 录制文件(cassette),如果请求没匹配到已有记录,直接报错 VCRFailureError: No match for the request。它不会主动写新文件——这其实是安全设计,防止意外覆盖或污染测试数据。
想让它自动生成,必须显式开启录制模式,并确保 cassette 文件路径可写、目录存在。
- 用
@vcr.use_cassette()时加参数record="once"或record="all" -
record="once":首次运行生成 cassette,后续复用;record="all":每次重录(适合调试,但 CI 中慎用) - 确保 cassette 路径父目录存在,否则会抛
IOError(不是 FileNotFoundError,容易误判) - 若用 pytest 参数全局控制,可加
--vcr-record=once,但需在 conftest.py 中配置vcr_config支持该参数
如何让每个测试生成独立的 cassette 文件?
硬编码文件名易冲突,推荐用测试函数名动态生成。常见做法是把 fixture 和 vcr.use_cassette() 结合,避免重复写路径逻辑。
示例(conftest.py 中定义 fixture):
@pytest.fixture
def vcr_cassette_name(request):
return f"{request.node.name}.yml"
然后在测试中:
@pytest.mark.vcr
def test_fetch_user(vcr_cassette_name):
with vcr.use_cassette(vcr_cassette_name, record="once"):
response = requests.get("https://api.example.com/user/123")
assert response.status_code == 200
- 文件名自动为
test_fetch_user.yml,清晰可追溯 - 不要用
__file__拼路径——跨平台时斜杠问题多,且 pytest 可能从不同路径执行 - 避免在
use_cassette()中写死绝对路径;相对路径以当前测试文件所在目录为基准,更可靠
record="new_episodes" 和 record="all" 有什么实际区别?
两者都会写入新请求,但处理已有匹配项的方式不同,直接影响 cassette 文件体积和稳定性。
-
record="all":无视已有匹配,所有请求都重录,整个 cassette 被覆盖 —— 适合彻底刷新数据,但会丢失手动编辑过的注释或裁剪 -
record="new_episodes":只追加新请求,已有匹配项不变 —— 更安全,适合增量更新接口字段,但要注意:如果请求参数微变(如时间戳、token),可能被识别为“新 episode”,导致 cassette 膨胀 - 真实场景中,
record="once"最常用;调试时临时切new_episodes,完事立刻切回 - 别在 CI 环境用
all或new_episodes,除非你明确要更新快照
为什么 cassette 文件里有时出现 base64 编码的 body?
这是 pytest-vcr(底层 vcrpy)对二进制响应体的默认处理策略:当响应头 Content-Type 不含 text/ 或 application/json 等可读类型时,自动 base64 编码 body 字段,避免 yaml/json 解析失败或乱码。
- 常见于图片、PDF、zip 下载类接口 —— 这是正常行为,不是 bug
- 如果希望强制文本化(比如 mock 一个返回纯文本但没设 header 的 API),可在
use_cassette()中加decode_compressed_response=True - cassette 文件体积会因此变大,但可读性提高;若只关心 status/code/header,可删掉 body 字段再提交(vcrpy 允许缺失 body)
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











