pytest 终结钩子报错后下一测试复用了旧 fixture,检查缓存清理
pytest 的终结钩子异常跳过缓存失效时会使后续测试复用旧 fixture,应采用修复版本并保留原 teardown 错误。
第一个测试在 teardown 的 pytest_fixture_post_finalizer 钩子中报错,第二个测试却拿到了第一个测试的 fixture 值,要检查钩子异常是否中断了缓存失效。对于函数作用域 fixture,后一个测试通常应重新建立自己的值;不能因为前一个 teardown 出错,就悄悄沿用旧对象。
旧路径直接调用这个钩子,之后才清空 fixture 的缓存结果。钩子抛出异常时,清理没有执行,下一次请求看见旧缓存就可能直接返回。它与“终结钩子被重复调用”是不同的故障,不能靠要求钩子幂等来恢复已经跳过的缓存清理。
pytest 9.1.1 包含对应修复:将钩子纳入终结器栈,按统一路径收集异常,随后仍完成缓存失效。原钩子错误应继续作为 teardown 错误报告;修复不会把插件异常当作成功。
python -m pip install "pytest==9.1.1"
在独立测试目录中,conftest.py 可临时放入一个故意失败的钩子:
import pytest
@pytest.hookimpl(trylast=True)
def pytest_fixture_post_finalizer(fixturedef, request):
if fixturedef.argname == "token" and request.node.name == "test_first":
raise RuntimeError("intentional finalizer failure")
test_cleanup.py 用每个请求的节点名辨别是否重新构建:
import pytest
@pytest.fixture
def token(request):
return request.node.name
def test_first(token):
assert token == "test_first"
def test_second(token):
assert token == "test_second"
python -m pytest -v --setup-show test_cleanup.py
预期两个测试函数都通过,第一个另有一次 teardown 错误,第二个取得自己的节点名。整个会话仍应失败退出,因为故意制造的钩子异常没有消失。验收时区分测试调用失败和 teardown 错误,再移除临时故障钩子,重新跑真实插件流程。
业务钩子仍需处理自己的资源和异常。升级只保证这一清理路径不会被钩子异常提前打断,不修复钩子内已经失败的外部操作,也不保证所有 setup、参数化或其他插件生命周期都正常。若原来错误时有写入副作用,应按业务状态判断恢复范围。
不要将 fixture 改成 session 作用域来隐藏第二个测试的错误值,那会改变资源共享语义。保留原作用域、原插件入口及顺序,才有助于确认这项缓存失效修复。


