unittest.mock.patch失效主因是模块提前加载导致引用固化:被测模块若在patch前已导入,mock仅修改缓存模块命名空间,而被测代码持有旧引用;from导入使patch目标错位;sys.modules残留引发测试污染;reload与mock混用造成新旧对象引用撕裂。

测试前模块已加载,mock失效
unittest.mock.patch 作用于模块对象的属性(如函数、类),但前提是 patch 目标在 patch 时已存在。如果被测模块 A 在测试用例导入前已被其他测试或初始化代码 import 过,sys.modules["A"] 中已有完整模块对象,此时再对 A.some_function patch,实际修改的是缓存中那个“旧模块”的命名空间——而你的被测代码可能早已从 sys.modules 拿到它并持有引用,导致 mock 不生效。
常见现象:patch 返回值没变、断言失败、日志里仍打印真实调用。
- 检查是否在
conftest.py或__init__.py中提前 import 了待测模块 - 避免在测试文件顶层 import 被测模块;改用函数内 import 或
importlib.import_module() - 使用
patch的autospec=True参数可减少因模块未加载导致的误判
测试后模块残留影响后续用例
一个测试用例执行 import mymodule 后,mymodule 就永久留在 sys.modules 中。下一个测试若也依赖 mymodule,哪怕它做了 patch 或修改了全局状态,拿到的仍是上一个测试污染后的模块对象——比如数据库连接未关闭、单例已初始化、配置被覆盖。
这会造成“测试顺序敏感”“单独跑通过、一起跑失败”等典型问题。
立即学习“Python免费学习笔记(深入)”;
- 在
tearDown或pytest.fixture的yield后手动清理:sys.modules.pop("mymodule", None) - 注意:仅删
sys.modules键不够,还需确保无其他变量(如from mymodule import func导入的名称)持有旧对象引用 - 更稳妥的做法是用
importlib.reload()替代删除——但它要求模块支持重载(不能有 C 扩展、不能依赖不可重入的初始化逻辑)
from ... import 导入让 mock 更难定位
当你写 from requests import get,get 就变成当前模块的局部变量,和 requests.get 不再是同一个对象。此时 patch "requests.get" 完全无效,因为被测代码调用的是自己命名空间里的 get 函数。
这是 mock 最常踩的坑之一,错误率极高。
- 优先使用
import requests,然后 patch"requests.get" - 若必须用
from ... import,patch 目标应为被测模块内的名称,例如 patch"mymodule.get"(假设被测代码在mymodule.py中写了from requests import get) - 用
print(mymodule.get)或assert mymodule.get is not requests.get快速验证引用关系
mock 和 reload 混用时的引用撕裂
在热重载测试中,有人会先 del sys.modules["mymodule"],再 importlib.reload(),同时还在 patch 某个函数。这时容易出现“新模块中函数被 mock,但旧类实例仍调用原始实现”的情况——因为旧实例的 self.__class__.method 指向的是 reload 前的函数对象。
这不是 bug,而是 Python 对象模型的必然结果。
- reload 后立即重建所有依赖对象(如重新实例化类、重连信号、重注册回调)
- 避免在模块顶层创建全局单例或长生命周期对象;把初始化推迟到函数调用时
- 测试中尽量不依赖跨模块的函数/类引用,改用依赖注入或工厂函数
sys.modules 里的键,删不掉其他模块里对它的 import 引用,也删不掉已经创建的实例对其方法的绑定。


















