pytest 报 ScopeMismatch:长生命周期 fixture 不能依赖短生命周期对象
pytest 长范围 fixture 依赖短范围资源时报 ScopeMismatch。按依赖链缩短缓存范围,或拆分长期初始化与逐测试状态,保留事务、临时资源和清理的隔离责任。
把 fixture 的 scope 改为 module 或 session 后,测试在 setup 阶段停止,错误包含 ScopeMismatch: You tried to access the function scoped fixture。这通常意味着长期保留的 fixture 请求了一个每个测试结束就要销毁的依赖。
先读错误中两侧的范围:function scoped fixture 是被请求的依赖,module scoped request object 或 session scoped request object 是请求方。下面针对 pytest 的普通 fixture 生命周期;异步 fixture 的事件循环范围还需要单独核对。
从报错中的依赖链找需要调整的一层
这个示例中,items 默认为 function 范围,shared_state 却打算缓存一整次测试会话:
import pytest
@pytest.fixture
def items():
return []
@pytest.fixture(scope="session")
def shared_state(items):
return {"items": items}
pytest 不能让 shared_state 持有某一次测试的 items,然后继续供其他测试使用。它按范围规则拒绝这条依赖,即使返回的是一个简单列表,即使目前只收集到一个测试,也不会因此获得合法的 session 生命周期。
范围从短到长为 function、class、module、package、session。请求方可以依赖与自己同范围或更长范围的 fixture;更长范围的 fixture 不能依赖更短范围的 fixture。先沿错误给出的 requesting fixture stack 查找,检查参数中的直接依赖,也检查通过 request.getfixturevalue() 取得的依赖。
不要只看自己编辑的文件。同名 fixture 可能在更近的 conftest.py、测试模块或插件中被覆盖。应确认当前测试实际解析到哪一个定义,以及它的真实 scope;autouse=True 只是自动使用,不会延长依赖的生命。
需要每个测试隔离状态时,缩短请求方范围
如果每个测试都应获得新的列表,保持 items 为 function,并把请求方恢复为默认范围:
import pytest
@pytest.fixture
def items():
return []
@pytest.fixture
def state(items):
return {"items": items}
def test_initial_state(state):
assert state["items"] == []
fixture 放在合适的 conftest.py 中,仍可被多个测试文件使用。可发现范围与缓存生命周期是两回事;不必为了让其他文件请求它,就把 scope 提升为 session。
tmp_path、monkeypatch 或插件提供的逐测试数据库隔离 fixture 都可能有明确的清理责任。长期 fixture 依赖它们时,应先检查是否真的需要长期缓存,而不是修改插件定义,把逐测试回滚或恢复机制变成全局状态。
昂贵初始化和可变状态可以分开
如果初始化确实昂贵,可长期保存不随测试变化的配置,再由短期 fixture 创建每次测试所需的对象。下面用不可变元组演示这种方向;实际初始化逻辑应按项目替换:
import pytest
@pytest.fixture(scope="session")
def seed_values():
return ("alpha", "beta")
@pytest.fixture
def items(seed_values):
return list(seed_values)
def test_can_modify_items(items):
items.append("gamma")
assert items == ["alpha", "beta", "gamma"]
def test_starts_from_seed(items):
assert items == ["alpha", "beta"]
这里是 function fixture 依赖 session fixture,方向合法。每个 items 列表重新创建,测试修改不会回写到元组。若种子是嵌套可变对象,浅拷贝仍可能共享内部内容,需按数据结构决定复制方式。
需要全会话共享数据库服务或浏览器进程时,也应把“服务存活多久”与“每个测试的事务、连接、浏览器上下文和数据如何隔离”分开。一个长期服务可以供短期 fixture 使用;不能据此让长期对象反向持有短期事务。
确需长期资源时,核对整条依赖和清理责任
只有当依赖也能在同样或更长范围正确存活,并允许测试共享它时,才考虑同时扩大范围。确认它不引用逐测试节点、不依赖逐测试临时目录,也不会把一个测试的修改带给下一个测试。
例如长期目录应评估 tmp_path_factory 的适用性,而不是直接把 tmp_path 包进 session fixture。pytest-django 的 db 还承担数据库隔离;绕过数据库访问保护或改成更长范围,不会自动保留原来的逐测试事务回滚。如果需要特殊数据库初始化,应按插件支持的初始化流程设计,并明确谁恢复数据。
成功标准包括 setup 能完成,以及测试之间仍保持预期隔离。单独运行一个测试不报错还不够;按原入口运行相关测试组,核对先后次序改变时的结果、清理是否执行、共享资源是否按约定结束。参数化 fixture 也可能在同一 scope 内被重新创建,不能把 session 理解成任何情况下都只创建一次。
保留修改前的 fixture 定义。若扩大范围后出现依赖顺序的失败,应恢复原 scope,并重做资源与状态的拆分;不要继续把更多 fixture 改成 session 来掩盖新的冲突。


