直接 patch open 会失败是因为必须 patch 被测模块中解析到的 open 名称空间,而非 builtins.open;正确做法是 @patch("mymodule.open", new_callable=mock_open, read_data="test content")。

为什么直接 patch open 会失败?
因为 Python 的 open 是内置函数,而 pytest 中 mock 默认只 patch 模块层级的引用。如果你在被测模块里写了 with open("data.txt") as f:,实际调用的是该模块的全局 open,但 mock 时若写成 @patch("builtins.open")(Python 3)或 @patch("__builtin__.open")(Python 2),却 patch 错了位置——必须 patch **被测代码所在模块中解析到的 open 名称空间**,而不是调用方的。
常见错误现象:FileNotFoundError 依然抛出,或 mock 对象没被调用;mock_open 返回的 file-like 对象读取为空、read() 不返回预期内容。
- 正确做法:用
@patch("mymodule.open", new_callable=mock_open, read_data="test content"),其中mymodule是你测试的模块名 - 如果被测函数用到了
pathlib.Path.open(),得 patchpathlib.Path.open,不是builtins.open -
mock_open默认不支持__iter__,遍历文件需手动启用:mock_open(read_data="a\nb\nc\n").return_value.__iter__.return_value = iter(["a\n", "b\n", "c\n"])
如何让 mock_open 正确响应不同模式('r', 'w', 'rb')?
mock_open 默认只模拟文本读取行为,对 'w' 或 'rb' 等模式无感知,也不会记录写入内容。你需要显式控制它的返回值和副作用。
- 写入场景:用
side_effect捕获写入调用,或用mock_open()配合.write()断言:mock_file.write.assert_called_with("hello") - 二进制模式:传入
read_data=b"\x00\x01",并确保被测代码中打开方式是'rb',否则类型不匹配会报错 - 避免陷阱:不要依赖
mock_open().return_value.read()返回值去断言——它总是返回read_data,哪怕你实际调用了readline()或read(3);如需精确控制,改用自定义MagicMock实例
测试涉及 os.path.exists 或 shutil.copy 的文件操作链
单一 patch open 不够,真实逻辑常伴随路径检查、复制、删除等组合操作。这些函数来自不同模块,必须分别 patch,且顺序和作用域要匹配。
- 同时 patch 多个:用
@patch("mymodule.os.path.exists")和@patch("mymodule.shutil.copy"),注意模块前缀必须一致 - 路径判断逻辑易出错:比如
os.path.exists("config.json")返回False,但被测代码可能拼接了绝对路径,这时 mock 的参数断言要带完整路径或用assert_called_with校验 -
shutil.copy的源/目标路径顺序不能颠倒;mock 后记得检查是否被调用:mock_copy.assert_called_once(),否则可能是路径条件未满足导致跳过
pytest + unittest.mock 混用时的常见兼容问题
pytest 本身不提供 mock,底层仍依赖 unittest.mock。但 pytest 的 fixture 和 autouse 机制容易和 mock 的生命周期冲突。
- 别在 autouse fixture 里做全局 patch:会导致多个测试间状态污染,改用函数级
@patch或 fixture 中显式 start/stop - 用 fixture 封装 mock 更可控:
@pytest.fixture def mocked_open(): with patch("mymodule.open", new_callable=mock_open, read_data="OK") as m: yield m - mock 对象在 assert 前可能已被垃圾回收(尤其在 with 块外),断言要放在 patch 作用域内,或保留对 mock 对象的引用
最麻烦的不是写法,而是搞清“哪个模块里的哪个名字”被实际 import 和调用——花十分钟确认 import 路径,比花一小时 debug mock 不生效更值得。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











