应优先 patch builtins.open(Python 3.4+)或 __builtin__.open(Python 2),或 os.open,用真实 PermissionError 异常,避免 patch 模块内 import 的 open;需区分 PermissionError 与 FileNotFoundError;注意 monkeypatch 作用域、缓存引用及竞态条件下的 os.access + open 联合 mock。
如何用 monkeypatch 模拟
触发文件写入失败
直接 patch
函数本身容易漏掉底层系统调用,真正可靠的方式是 mock
或
底层入口。pytest 的
最适合做这种“让某次系统调用突然报错”的事。
常见错误是 patch 了模块里 import 的
,但被测函数实际调用的是内置
(比如没显式 import),结果 mock 完全不生效。
优先 patch
(Python 3.4+)或
(Python 2)
若逻辑走
(如用
),就 patch
报错必须用真实异常类,不能只 raise 字符串——否则
捕不到
读取权限测试:为什么
不等于权限问题
用户常误以为“读不了就是没权限”,其实
表示路径不存在,
才是真权限拒绝。Linux 下对目录无 x 权限、文件无 r 权限,或 Windows 下文件设为只读但代码尝试以写模式打开,才会触发后者。
测试时若只 mock
,根本覆盖不到真实权限逻辑分支。
立即学习
“
Python免费学习笔记(深入)
”;
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
下载
读场景下要分别模拟:
(有路径但不可读)、
(路径不存在)
注意:某些函数先
再 open,这时得 patch
才能控制前置检查行为
Windows 上只读文件用
会报
;Linux 下需手动
才能复现
的作用域和清理陷阱
pytest 默认在每个 test 函数结束后自动恢复被 patch 的对象,但如果你在测试中手动调用了
,或者在 fixture 里用了
却没管理好顺序,就可能污染后续测试。
避免在
的 module-scoped fixture 中做全局 patch,除非你明确需要跨 test 复用且负责清理
如果被测代码缓存了
引用(如
),patch
无效——得 patch 那个模块里的
想验证“重试逻辑”时,别只 raise 一次异常:用闭包或
控制第几次调用才失败
真实环境 vs 测试:为什么不能依赖
的返回值
很多代码会先调
判断可写再 open,但这在多线程/多进程下是竞态的——判断完瞬间文件权限可能就被改了。所以单元测试里 mock
返回
,却仍要 mock
报错,才能覆盖“判断通过但实际失败”的路径。
在容器或 NFS 挂载点上可能返回误导性结果,它只是建议,不是保证
测试重点应放在 open 失败后的降级处理(如写日志、fallback 到临时目录),而不是验证
是否准确
若被测函数同时用
和
,两个都得 patch,且顺序要匹配执行流
权限逻辑的脆弱点从来不在判断,而在“判断和操作之间的时间窗口”。测试时得把那个窗口强行撕开,塞进一个错误。
PermissionErroropenos.openio.openmonkeypatchopenopenbuiltins.open__builtin__.openos.openos.O_WRONLY | os.O_CREATos.openexcept PermissionError:def test_write_fails_on_permission_denied(monkeypatch):
def mock_open(*args, **kwargs):
raise PermissionError(13, "Permission denied")
monkeypatch.setattr("builtins.open", mock_open)
assert not write_config("/etc/app.conf", "data") # 假设该函数内部调用 open()
FileNotFoundErrorFileNotFoundErrorPermissionErrorFileNotFoundErrorPermissionErrorFileNotFoundErroros.stat()os.statopen(..., "w")PermissionErrorchmod -r filemonkeypatchmonkeypatch.undo()scope="module"conftest.pyopen_open = openbuiltins.open_openitertools.countfrom itertools import count
def test_retry_on_permission_error(monkeypatch):
counter = count()
def mock_open(*args, **kwargs):
if next(counter) == 0:
raise PermissionError("denied")
return io.StringIO("ok")
monkeypatch.setattr("builtins.open", mock_open)
os.access()os.access(path, os.W_OK)os.accessTrueopenos.accessos.accessos.accessopen