pytest中yield fixture清理的正确写法是:用@pytest.fixture装饰器定义函数,yield前写setup逻辑,yield后同缩进写清理代码,且yield必须是函数中最后一行可执行语句;setup失败则yield不执行、teardown不触发,测试异常或中断时teardown仍确保运行。

pytest中用yield fixture做清理的正确写法
直接说结论:用 @pytest.fixture 装饰器定义 fixture,函数体里用 yield 分隔“setup”和“teardown”逻辑,yield 后面的代码就是清理部分。别写成 return,也别漏掉 yield——否则清理根本不会执行。
yield fixture 的执行时机和作用域
yield fixture 的清理代码在测试函数(或测试类)退出后立即运行,哪怕测试抛异常也会触发。作用域(scope)决定它被复用的范围,常见组合:
-
scope="function":每个测试函数前后各跑一次 setup/teardown(最常用) -
scope="class":整个测试类只初始化一次,但清理会在类内所有测试跑完后执行一次 -
scope="module":模块级,注意多个测试文件可能共享同一模块作用域,清理可能晚于预期
错误做法是把数据库连接、临时文件创建等操作放在 setup 部分却配了 scope="session",结果后续测试读到脏数据。
常见错误:yield 后没写清理逻辑或变量作用域出错
典型翻车现场:
立即学习“Python免费学习笔记(深入)”;
- 写了
yield resource,但 yield 后面是空行或只有注释——清理代码实际没写 - 在 yield 前创建了局部变量(如
tmpfile = tempfile.mktemp()),却在 yield 后用os.remove(tmpfile)——如果测试中途崩溃,tmpfile可能未定义,导致 cleanup 报NameError - 误以为 yield 可以返回多个值(如
yield a, b),其实 fixture 只接收第一个值;多资源建议封装成字典或对象传回
稳妥写法是把资源创建和销毁都放在 fixture 函数内,并用 try/finally 包裹 yield 前逻辑(非必须,但防 setup 阶段就失败):
@pytest.fixture
def db_connection():
conn = sqlite3.connect(":memory:")
try:
yield conn
finally:
conn.close()
替代方案:addfinalizer 比 yield 更灵活吗?
不是更灵活,是更底层。addfinalizer 允许动态注册多个清理回调,适合条件式注册(比如只在某种配置下才清理缓存)。但日常用 yield 更直观、不易漏写。
- yield 是声明式,逻辑集中,易读易维护
- addfinalizer 需显式调用
request.addfinalizer(func),且清理函数不能带参数,要传参得用闭包或functools.partial - 两者都支持异常安全,但 addfinalizer 注册的函数若抛异常,pytest 会记录为 warning,不影响测试结果;而 yield 后的代码若抛异常,会被视为 teardown 失败,测试结果标为 “error”
真正容易被忽略的是:yield fixture 中的清理代码一旦抛异常,pytest 默认会中断后续所有 teardown(包括其他 fixture),所以清理逻辑本身要尽量轻量、防御性地处理异常,比如用 shutil.rmtree(path, ignore_errors=True) 而不是裸调 shutil.rmtree(path)。


















