yield 将 pytest fixture 拆分为初始化(yield 前)和清理(yield 后)两阶段,确保清理代码必执行;需 yield 带值(如 yield conn),否则测试函数获 None,且清理代码须在 yield 后、不加外层 try/finally。

yield 在 pytest fixture 里到底干了什么
它不是“返回值”,而是把 fixture 拆成两段执行:yield 前的部分在测试开始前运行(初始化),之后的部分在测试结束后运行(清理)。关键在于,pytest 会自动捕获 yield 后面的代码,并确保它被执行——哪怕测试崩溃、抛异常、提前 return,清理逻辑也不会被跳过。
常见错误现象:yield 写成了 return,或者忘了写 yield,结果数据库连接没关,跑几次就报 too many connections;又或者 yield 后面没加任何语句(比如只写了 yield 没接资源变量),导致测试函数收不到初始化后的对象。
-
yield必须带一个值(比如yield conn),否则测试函数拿到的是None - 清理代码必须写在
yield之后,且不能放在try/finally外层——pytest 自己用finally包裹了 yield 后逻辑,手动加反而可能干扰流程 - 如果初始化失败(比如数据库连不上),
yield不会执行,清理自然也不执行——这反而是对的,因为没东西要清
怎么写一个带 yield 的数据库 fixture
典型场景:每个测试需要独立的 sqlite 连接,测试完立刻 close(),避免连接泄漏。不用 yield,就得靠 addfinalizer 或者上下文管理器,但更啰嗦、易漏。
参数差异:注意 scope。设成 scope="function"(默认)最安全,每次测试都新建+关闭;设成 scope="session" 就得自己保证并发安全,而且 yield 后的清理只在 session 结束时跑一次——不适合数据库连接这类短生命周期资源。
立即学习“Python免费学习笔记(深入)”;
import sqlite3
import pytest
<p>@pytest.fixture
def db_connection():
conn = sqlite3.connect(":memory:") # 每次都是干净内存库
yield conn
conn.close() # 这行一定会执行使用时直接当参数名传进测试函数:def test_something(db_connection):,db_connection 就是那个 yield 出来的 conn 对象。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
yield 后出错,清理还生效吗
生效。pytest 的 fixture 执行机制决定了:只要 yield 语句执行过了,后面的代码块就进了“必执行区”。哪怕你在 yield 后面主动 raise ValueError("boom"),这个 raise 是在清理阶段发生的,不影响清理本身已启动的事实。
容易踩的坑:yield 后面写的是可能抛异常的操作(比如 conn.rollback() 或网络请求),但没做异常处理。这时候测试报告里会多一条“fixture teardown failed”,但不会掩盖原始测试失败——只是清理失败了,该关的连接可能真没关掉。
- 如果清理操作本身不可靠(如远程服务超时),建议用
try/except包住yield后的代码,至少不让清理失败中断整个 teardown 流程 - 不要在
yield后调用os._exit()或其他强制终止进程的操作,那会跳过所有后续 teardown - 调试时可加
print或日志到yield前后,确认清理是否真的触发了
为什么不用 contextlib.closing 或 with 语句替代 yield
可以,但不等价。with 只能保当前测试函数内的一次性清理,而 fixture 的 yield 是 pytest 生命周期管理的一部分,和测试调度强绑定。比如你用了 autouse=True,或者 fixture 被多个测试共享依赖,with 就完全失控了。
性能影响很小,但兼容性要注意:老版本 pytest(yield fixture 的异常传播有 bug,升级到 pytest>=6.0 更稳;另外,某些插件(如 pytest-asyncio)对 yield fixture 的协程支持有限,如果初始化或清理本身是 async,得用 async def + pytest-asyncio 的专用 fixture 语法,不能硬套 yield。
一句话:用 yield 不是为了炫技,是因为它把“什么时候初始化”“什么时候清理”这两个时间点,完全交给了 pytest 控制——而这是手工 with 或 try/finally 永远做不到的。
最常被忽略的点:yield 后的清理代码,无法访问测试函数里的局部变量,也不能直接 assert 或影响测试结果;它纯粹是“扫尾”,别指望它能帮你诊断测试失败原因。

















