pytest会因全局变量失败而“串用例”,因其默认按模块级别复用测试函数所在模块环境,import后的模块级变量(如config、cache、counter)在多个测试间不重置,导致一个测试修改后影响后续测试。

为什么 pytest 会因全局变量失败而“串用例”?
pytest 默认按模块级别复用测试函数所在的模块环境,import 后的模块级变量(比如 CONFIG = {}、CACHE = []、counter = 0)在多个测试函数间不重置,一个测试改了它,下一个就拿到脏数据。这不是 bug,是 Python 模块加载机制和 pytest 默认 scope 的自然结果。
常见现象包括:
- 测试 A 往
shared_list.append("x"),测试 B 断言len(shared_list) == 0失败 - 测试 C 修改了
os.environ["DEBUG"] = "1",测试 D 因环境变量误判逻辑分支 - 使用了单例类或模块级连接池,未清理导致连接超时或状态残留
用 fixture 清理模块级全局变量最稳妥
别靠 setUp/tearDown 或手动写 del,用 pytest fixture 的 scope="module" 或 scope="function" 控制生命周期,配合 yield 做前后清理。
例如清理一个模块级字典:
@pytest.fixture(autouse=True, scope="function")
def clean_config():
# 保存原始状态
original = dict(CONFIG)
yield
# 恢复(不是清空!避免影响其他非测试代码)
CONFIG.clear()
CONFIG.update(original)
关键点:
-
autouse=True确保所有测试都自动应用,不用每个函数都加参数 - 用
dict(CONFIG)浅拷贝,避免引用污染;若含嵌套可考虑copy.deepcopy - 不要直接
CONFIG = {}—— 这会重新绑定变量名,原模块里的引用失效 - 如果变量是
os.environ这种特殊对象,用monkeypatch.setenv()更安全
避免在测试模块里定义可变全局变量
很多问题其实源于写法习惯:把测试工具函数和临时数据全堆在 test_xxx.py 顶层。
应该:
- 把可变状态封装进类实例,每次测试 new 一个新对象:
db_helper = DatabaseHelper()而不是DB_HELPER = DatabaseHelper() - 用
tempfile.mktemp()或tmp_pathfixture 替代硬编码路径如/tmp/test.db - 配置类用
dataclass+default_factory,避免默认参数是可变对象:fields: list = field(default_factory=list) - 真需要模块级缓存?加个开关控制是否启用,测试时默认关闭
排查时优先看 import 链和模块重载行为
有时你以为的“全局变量”其实来自被多次 import 的模块 —— 比如 utils.py 被 app.py 和 test_xxx.py 同时导入,改的是同一份内存对象。
验证方式:
- 在测试中打印
id(CONFIG),对比不同测试函数是否一致 - 检查是否用了
importlib.reload(),它会重置模块但可能漏掉已创建的对象引用 - 运行时加
-s参数看实际执行顺序,确认 fixture 是否真在每个测试前触发 - 禁用缓存:加
--cache-clear或删掉.pytest_cache目录,排除缓存干扰
最麻烦的情况是 C 扩展模块或某些 ORM 维护的内部状态,这类往往得靠重启进程(即设 --fork 或用 xdist 的 --boxed)隔离,但代价高,应作为兜底手段。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











