monkeypatch.setattr不能直接改模块级变量,因为模块级变量是命名空间绑定名而非对象属性;正确做法是传入模块对象和变量名字符串,如monkeypatch.setattr(mymodule, 'debug', true)。

monkeypatch.setattr 为什么不能直接改模块级变量?
直接用 monkeypatch.setattr 修改模块顶层变量(比如 CONFIG_PATH 或 DEBUG)会失败,因为 pytest 的 monkeypatch 只能安全地 patch 对象的属性或类/实例的成员,而模块级变量不是“属性”——它是模块命名空间里的绑定名。常见错误是写成 monkeypatch.setattr('mymodule.DEBUG', True),这会报 AttributeError: module 'mymodule' has no attribute 'DEBUG',因为字符串路径解析时试图找属性而非全局变量。
正确做法是:必须指定目标模块对象本身,并传入变量名字符串和新值:
import mymodule <p>def test_with_global_var(monkeypatch): monkeypatch.setattr(mymodule, 'DEBUG', True) # ✅ 正确:传模块对象 + 变量名 assert mymodule.DEBUG is True</p>
修改内置模块变量(如 os.environ)要小心作用域
修改 os.environ 这类全局状态时,monkeypatch.setattr 依然适用,但要注意:它只替换整个 os.environ 对象(即 dict 实例),而不是深拷贝或隔离原有内容。如果测试中依赖原有环境变量(比如 PATH),直接覆盖会导致意外失败。
- ✅ 推荐方式:用
monkeypatch.setitem(os.environ, 'MY_VAR', 'test')—— 它只修改单个键,保留其他变量 - ⚠️ 避免:
monkeypatch.setattr(os, 'environ', {'MY_VAR': 'test'})—— 会清空所有环境变量 - ? 注意:
setitem只支持 mapping 类型(dict、os.environ),不适用于普通模块变量
patch 模块级常量时,导入方式影响 patch 效果
如果被测代码是通过 from mymodule import DEBUG 导入的常量,monkeypatch 改 mymodule.DEBUG 不生效——因为导入后,DEBUG 已变成当前模块的局部名称,和原模块无关。
解决方法只有两个:
- ✅ 在被测代码所在模块里 patch(即 patch “使用方”模块的
DEBUG名) - ✅ 改用绝对导入路径:在测试中写
monkeypatch.setattr('target_module.DEBUG', 'new_value'),其中target_module是实际使用该变量的模块名(不是定义它的模块) - ❌ 不要 patch 定义模块,除非被测代码是用
import mymodule+mymodule.DEBUG方式访问
临时修改 sys.path 或其他不可变全局状态
sys.path 是 list,可直接用 monkeypatch.setattr 替换,但更安全的是用 monkeypatch.setattr(sys, 'path', new_path_list)。不过要注意:
- pytest 默认会在每个测试后自动恢复
sys.path,所以无需手动清理 - 如果测试中调用了
import,且依赖修改后的sys.path,请确保 patch 在 import 之前执行(monkeypatch 是函数作用域,顺序很重要) - 对
sys.argv等同样适用,但避免在 fixture 中提前 patch——可能被后续测试干扰
真正容易被忽略的是:monkeypatch 的生命周期只到测试函数结束;如果你在 fixture 中 patch 并期望跨测试生效,那是错的——每个测试都是干净的,别指望“全局临时修改”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











