应优先 patch builtins.open(Python 3.4+)或 __builtin__.open(Python 2),或 os.open,用真实 PermissionError 异常,避免 patch 模块内 import 的 open;需区分 PermissionError 与 FileNotFoundError;注意 monkeypatch 作用域、缓存引用及竞态条件下的 os.access + open 联合 mock。

如何用 monkeypatch 模拟 PermissionError 触发文件写入失败
直接 patch open 函数本身容易漏掉底层系统调用,真正可靠的方式是 mock os.open 或 io.open 底层入口。pytest 的 monkeypatch 最适合做这种“让某次系统调用突然报错”的事。
常见错误是 patch 了模块里 import 的 open,但被测函数实际调用的是内置 open(比如没显式 import),结果 mock 完全不生效。
- 优先 patch
builtins.open(Python 3.4+)或__builtin__.open(Python 2) - 若逻辑走
os.open(如用os.O_WRONLY | os.O_CREAT),就 patchos.open - 报错必须用真实异常类,不能只 raise 字符串——否则
except 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()
读取权限测试:为什么 FileNotFoundError 不等于权限问题
用户常误以为“读不了就是没权限”,其实 FileNotFoundError 表示路径不存在,PermissionError 才是真权限拒绝。Linux 下对目录无 x 权限、文件无 r 权限,或 Windows 下文件设为只读但代码尝试以写模式打开,才会触发后者。
测试时若只 mock FileNotFoundError,根本覆盖不到真实权限逻辑分支。
立即学习“Python免费学习笔记(深入)”;
- 读场景下要分别模拟:
PermissionError(有路径但不可读)、FileNotFoundError(路径不存在) - 注意:某些函数先
os.stat()再 open,这时得 patchos.stat才能控制前置检查行为 - Windows 上只读文件用
open(..., "w")会报PermissionError;Linux 下需手动chmod -r file才能复现
monkeypatch 的作用域和清理陷阱
pytest 默认在每个 test 函数结束后自动恢复被 patch 的对象,但如果你在测试中手动调用了 monkeypatch.undo(),或者在 fixture 里用了 scope="module" 却没管理好顺序,就可能污染后续测试。
- 避免在
conftest.py的 module-scoped fixture 中做全局 patch,除非你明确需要跨 test 复用且负责清理 - 如果被测代码缓存了
open引用(如_open = open),patchbuiltins.open无效——得 patch 那个模块里的_open - 想验证“重试逻辑”时,别只 raise 一次异常:用闭包或
itertools.count控制第几次调用才失败
from 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)
真实环境 vs 测试:为什么不能依赖 os.access() 的返回值
很多代码会先调 os.access(path, os.W_OK) 判断可写再 open,但这在多线程/多进程下是竞态的——判断完瞬间文件权限可能就被改了。所以单元测试里 mock os.access 返回 True,却仍要 mock open 报错,才能覆盖“判断通过但实际失败”的路径。
-
os.access在容器或 NFS 挂载点上可能返回误导性结果,它只是建议,不是保证 - 测试重点应放在 open 失败后的降级处理(如写日志、fallback 到临时目录),而不是验证
os.access是否准确 - 若被测函数同时用
os.access和open,两个都得 patch,且顺序要匹配执行流
权限逻辑的脆弱点从来不在判断,而在“判断和操作之间的时间窗口”。测试时得把那个窗口强行撕开,塞进一个错误。


















