yield前异常导致清理完全跳过:若yield前代码(如数据库连接)抛出异常,fixture初始化失败,yield后清理代码永不执行;须用try/finally确保关键清理。

yield前异常导致清理完全跳过
这是最常见的原因:如果 yield 之前的代码(比如连接数据库、创建临时目录)抛出异常,pytest 根本不会执行 yield 后的任何语句——因为 fixture 被判定为“初始化失败”,资源压根没拿到,也就谈不上释放。
例如:
@pytest.fixture
def bad_db_fixture():
conn = sqlite3.connect("/invalid/path.db") # 这里 OSError: unable to open database file
yield conn
conn.close() # ❌ 永远不会走到这里
- 检查
yield前所有操作是否可能失败(文件权限、网络不通、路径不存在、SQL 语法错) - 若必须确保清理(比如已创建了临时进程或文件),用
try/finally包裹整个逻辑,而非依赖yield
fixture 未被测试函数实际引用
pytest 只对**被测试函数参数显式声明**的 fixture 才会执行其 setup/teardown 流程。如果写了 fixture 却没在测试函数签名里写上它名字,pytest 就当它不存在。
错误写法:
立即学习“Python免费学习笔记(深入)”;
@pytest.fixture
def temp_dir():
d = tempfile.mkdtemp()
yield d
shutil.rmtree(d) # ❌ 这段永远不会运行
<p>def test_something(): # 没写参数 temp_dir → fixture 不触发
assert True
- 确认测试函数签名中包含该 fixture 名(如
def test_something(temp_dir):) - 避免同名 fixture 在多个
conftest.py中重复定义,pytest 可能选错作用域 - 检查是否误用了
autouse=True却又没处理好作用域冲突
pytest 版本太旧或语法写错
早期 pytest(yield 作为 teardown 机制;另外,yield 必须是 fixture 函数里的**最后一行可执行语句**,后面不能跟其他逻辑(哪怕只是注释或空行)。
无效写法:
@pytest.fixture
def broken_yield():
yield "data"
print("cleanup") # ❌ 这行在 yield 后,但 yield 不是函数末尾 → pytest 忽略整段 teardown
- 运行
pip show pytest,确保版本 ≥ 3.0(推荐 ≥ 7.0) -
yield后不要加任何语句;清理逻辑必须紧贴yield下方,且是函数自然结束前的最后执行块 - 注意缩进:yield 和后续代码必须在同一缩进层级,否则语法错误直接中断 fixture 加载
scope 作用域与执行时机不匹配
scope 决定了 cleanup 的触发时机:function 是每个测试后执行,module 是整个模块最后一个测试后执行,session 是全部测试跑完才执行。如果误设了大 scope,你可能误以为“没执行”,其实是还没到时间。
例如:
@pytest.fixture(scope="module")
def db_per_module():
conn = sqlite3.connect(":memory:")
yield conn
conn.close() # ✅ 会在整个 test_module.py 最后一个测试结束后才调用
- 用
print()或日志在 yield 前后打点,确认执行流是否真卡住,还是只是延迟触发 - 调试时优先用
scope="function",排除作用域干扰 - 注意:多个 fixture 间依赖时,teardown 顺序是反向的(最右参数的 fixture 先 teardown)
真正容易被忽略的是:yield 不是魔法开关,它只保证“只要 yield 成功执行,后续代码就一定进 teardown 队列”。而 yield 是否成功,取决于它前面所有语句是否静默完成——中间任何未捕获的异常,都会让整个 teardown 彻底失效。


















