因为open是内置函数,patch需作用于其被导入的位置(如"utils.open"),而非定义位置"builtins.open";否则mock不生效,真实文件操作仍执行。

为什么直接 patch open 会失败?
因为 Python 的 open 是内置函数,它在模块的全局命名空间里,但 pytest-mock(底层用的是 unittest.mock.patch)默认 patch 的是「目标对象被导入的位置」,不是定义位置。比如你在 my_module.py 里写了 with open("data.txt") as f:,就得 patch my_module.open,而不是 builtins.open(Python 3)或 __builtin__.open(Python 2)。否则 mock 不生效,真实文件操作照常发生。
常见错误现象:FileNotFoundError 仍抛出,或断言 mock 调用次数为 0。
- 确认被测函数所在模块名,比如
utils.py,就 patch"utils.open" - Python 3 下不要写
"builtins.open"—— 除非你明确在测试文件里 import 了 builtins 并直接调用它 - 如果被测代码在
__main__中运行(如脚本式代码),需 patch"__builtin__.open"或"builtins.open",但这种情况应尽量避免
用 mocker.patch 模拟读文件(open 返回 StringIO)
最稳妥的方式是让 mock 的 open 返回一个可读的类文件对象,比如 io.StringIO(文本模式)或 io.BytesIO(二进制模式)。这样能兼容 read()、readlines()、上下文管理等行为。
import io
from mypackage.utils import load_config
<p>def test_load_config(mocker):</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/00968c3c2c15" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">Python免费学习笔记(深入)</a>”;</p><h1>模拟 open 返回一个含 JSON 内容的 StringIO</h1><pre class="brush:php;toolbar:false;">mock_file = io.StringIO('{"host": "localhost", "port": 8080}')
mocker.patch("mypackage.utils.open", return_value=mock_file)
result = load_config()
assert result == {"host": "localhost", "port": 8080}
注意:这里 return_value=mock_file 只处理了单次调用;若函数中多次 open(如先读再写),需用 side_effect 返回不同对象。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
- 必须手动调用
mock_file.seek(0)后续才能重复读取,否则第二次read()返回空字符串 - 如果被测函数用了
encoding参数(如open(..., encoding="utf-8")),mock 不校验参数,但你要确保StringIO内容是合法字符串 - 不推荐用
return_value=mocker.mock_open(read_data="...")—— 它对readline()和迭代行为支持不稳定,容易在复杂逻辑中翻车
模拟写文件并验证内容是否正确写入
要验证写入行为,关键是捕获写入的目标对象(即 mock 返回的文件句柄),然后检查它的 write 方法是否被调用、传入什么参数。
def test_save_report(mocker):
mock_file_obj = mocker.mock_open()
mocker.patch("reports.generate.open", mock_file_obj)
<pre class="brush:php;toolbar:false;">save_report({"status": "ok"})
# 验证 open 被以 "w" 模式调用
mock_file_obj.assert_called_once_with("report.json", "w", encoding="utf-8")
# 验证 write 被调用,且内容包含预期字符串
handle = mock_file_obj()
handle.write.assert_called_once()
assert '"status": "ok"' in handle.write.call_args[0][0]
注意 mocker.mock_open() 返回的是一个 mock 工厂,第一次调用它(mock_file_obj())才拿到那个“假文件句柄”对象。
- 如果被测函数用的是
print(..., file=f),需额外 patchbuiltins.print或确保 mock 文件对象有write和flush方法 - 二进制写入(
"wb")要用BytesIO,且write参数得是bytes,否则类型错误 - 别忘了检查 mode 和 encoding 是否匹配——mock 不报错,但真实逻辑可能因编码不一致崩溃
遇到 OSError: [Errno 9] Bad file descriptor 怎么办?
这通常是因为 mock 的文件对象缺少关键方法(如 __enter__、__exit__、close),而被测代码在 with 语句结束时试图调用它们。原生 mock_open 默认不带完整上下文协议支持。
解决方案:手动补全协议方法,或改用 io.StringIO + 显式设置 mock 行为:
def test_with_open_context(mocker):
mock_file = mocker.MagicMock()
mock_file.__enter__.return_value = mock_file
mock_file.__exit__.return_value = None
mock_file.read.return_value = "fake content"
<pre class="brush:php;toolbar:false;">mocker.patch("mymodule.open", return_value=mock_file)
result = read_from_file()
assert result == "fake content"
- 直接用
mocker.MagicMock更可控,尤其当你要精细控制__iter__(for line in f)、readline()等行为时 - 如果被测函数调用了
f.close(),mock 对象必须有该方法(哪怕只是pass),否则抛AttributeError - 别依赖
mock_open的自动上下文支持——它的__enter__默认返回自身,但__exit__可能没设,导致 Bad file descriptor
真正麻烦的从来不是怎么 mock,而是被测函数里那些没显式关闭、混用文本/二进制模式、或依赖文件指针位置的细节——这些地方 mock 很难覆盖,得靠重构来降低耦合。

















