yield fixture 更适合清理,因为它将fixture拆分为setup→测试执行→teardown三段,确保无论测试异常、跳过或失败,teardown均能执行;而return仅支持setup,需额外机制补cleanup,易遗漏。

yield fixture 为什么比 return 更适合清理
因为 yield 把 fixture 拆成“setup → 测试执行 → teardown”三段,中间抛异常、测试跳过、用例失败,teardown 都能保证运行;而 return 只能做 setup,后续得靠额外机制(比如 addfinalizer)补 cleanup,容易漏。
常见错误现象:pytest 运行完发现数据库表没清空、临时文件还在、mock 还挂着——大概率是用了 return 或者在 yield 后面写了清理但没包进 try/finally。
- 必须把清理逻辑写在
yield之后、且包裹在finally块里(或直接跟在yield后,因 yield 本身已隐含 finally 行为) - 不要在
yield前 raise 异常,否则 teardown 不触发;真要提前退出,用pytest.skip()或让 setup 自身 fail - 如果清理动作可能失败(比如删文件时权限不足),别让它中断整个 teardown 流程——用
try/except吞掉非关键异常
数据库测试数据清理的典型写法
持久化测试最怕脏数据跨用例污染,尤其用 SQLite 文件或本地 PostgreSQL 实例时。fixture 的 yield 机制刚好卡在测试结束那一刻执行 truncate/drop。
使用场景:每个测试用例需要干净的 users 表,且不希望每次重建整库(太慢)。
import pytest
from sqlalchemy import create_engine
<p>@pytest.fixture
def clean_db():
engine = create_engine("sqlite:///test.db")</p><h1>setup:确保表存在</h1><pre class="brush:python;toolbar:false;">with engine.connect() as conn:
conn.execute("DELETE FROM users")
conn.commit()
yield engine
# teardown:再清一次,防测试中途 crash 留下脏数据
with engine.connect() as conn:
conn.execute("DELETE FROM users")
conn.commit()
-
yield前后都清一次是保守做法,适合低频、高可靠性要求场景 - 如果用的是内存 SQLite(
sqlite:///file::memory:),每次 fixture 新建引擎就天然隔离,反而不用清——但要注意是否真共享了同一个连接实例 - PostgreSQL 测试建议配合
pg_dump快照 +pg_restore回滚,比逐条 DELETE 快得多;此时 yield fixture 里更适合调 shell 命令而非 SQL
文件类 fixture 清理的坑:路径和权限
测试中生成的临时目录、日志、上传文件,不清理会越积越多,还可能因路径冲突导致后续测试失败。
常见错误现象:OSError: [Errno 13] Permission denied 或 FileNotFoundError 在 teardown 阶段报出,但测试本身通过了——说明清理失败被静默吞掉。
- 永远用
tempfile.mkdtemp()创建路径,别手写/tmp/mytest_123,避免冲突和权限问题 - 清理时优先用
shutil.rmtree(),别用os.remove()去删目录;删前加if os.path.exists()防止路径已被其他测试删过 - Windows 下注意长路径、只读文件(比如日志被打开着),可加
onerror参数强制处理
多个 yield fixture 嵌套时的执行顺序
当一个测试同时依赖 clean_db 和 tmpdir,pytest 按作用域和声明顺序决定 teardown 先后。scope 越小(function)越先 teardown,同 scope 按 fixture 定义顺序逆序执行。
性能影响:嵌套太深或 teardown 太重(比如每次重启 Docker 容器),会让测试变慢;但比起用例间数据污染,这点开销通常值得。
- 避免在
session级 fixture 里 yield 本地文件路径——不同测试函数可能并发写同一路径 - 如果两个 fixture 都要操作同一资源(比如都连同一个 DB),显式声明依赖关系(
def my_fixture(clean_db):),别靠隐式顺序 - 调试 teardown 执行情况,加
print()或日志到stderr,pytest 默认不捕获 teardown 输出
最易被忽略的一点:yield fixture 的 teardown 是同步执行的,但如果你在里面 await 异步操作(比如 await aiomysql.close()),它不会等——得用 async def + pytest-asyncio 适配,否则清理根本没发生。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











