推荐用 fs.fs 抽象文件系统接口,传入 mock 实现并返回 &fs.patherror{op: "write", path: name, err: syscall.enospc};避免 monkey patch,优先重构函数签名;注意 errors.is 需先断言 *os.patherror 再比内层 err。

如何用 fs.FS 替换真实文件系统做磁盘故障测试
直接 patch os.WriteFile 或在测试里调用真实磁盘 I/O,既不可靠又难清理。Go 1.16+ 推荐的解法是抽象文件系统接口——把所有文件操作封装成接收 fs.FS 参数的函数,再传入一个可控的 mock 实现。
关键不是“怎么伪造错误”,而是“让错误发生在正确的位置”:比如写入失败必须返回 syscall.ENOSPC,而不是 errors.New("disk full"),否则上层逻辑(如重试、清理)根本不会触发。
- 用
io/fs接口定义行为,例如实现func Open(name string) (fs.File, error)方法,在内部按路径/大小策略返回不同错误 - 对写操作,返回
&fs.PathError{Op: "write", Path: name, Err: syscall.ENOSPC},而非裸 err - 避免用
os.CreateTemp填满磁盘——速度慢、权限受限、清理易遗漏;mock 更快更稳 - 若原函数没接受
fs.FS,优先重构签名,而不是强行 monkey patch —— 后者在构建时可能被内联优化掉
为什么 errors.Is(err, syscall.ENOSPC) 总是 false
因为 os.File.Write 失败时返回的是 *os.PathError,它的 Err 字段才是真正的 errno。直接拿整个 error 去比 syscall.ENOSPC,等于拿包装盒去比里面的螺丝钉。
典型错误写法:if errors.Is(err, syscall.ENOSPC) → 永远不成立;正确路径是先拆包,再比内层。
- 先做类型断言:
if pe, ok := err.(*os.PathError); ok && errors.Is(pe.Err, syscall.ENOSPC) - Windows 下需用
syscall.ERROR_DISK_FULL,别硬写ENOSPC - 如果用了
io.Copy,还要额外检查err == io.ErrShortWrite场景——它不等价于磁盘满,但可能是前兆
用 failpoint 注入磁盘满而不改业务代码
当无法重构函数签名或需要在集成测试中快速验证异常路径时,github.com/pingcap/failpoint 是更轻量的选择。它不依赖接口抽象,而是通过编译期注入点控制执行流。
比如在日志写入前加一个 failpoint:failpoint.Inject("write_disk_full", func(val failpoint.Value) { if val.(bool) { panic(syscall.ENOSPC) } }),然后用环境变量 FAILPOINTS="write_disk_full=return(true)" 触发。
- failpoint 不修改源码结构,适合已有项目快速接入
- 注意 panic 后的 recover 必须覆盖到该调用栈,否则测试会中断
- 别在生产构建中启用 failpoint——它默认只在
go test -tags failpoint下生效 - 比手动 mock 更适合验证跨多层调用的错误传播,比如 HTTP handler → service → repo → fs.WriteFile
测试 os.Stdout 被占满时的行为要小心 Close()
很多 CLI 工具把输出直写 os.Stdout,想测磁盘满就替换成 mock writer。但 os.Stdout 是带缓冲的 *os.File,mock 若没实现 io.Closer 或 Close() 返回非 nil 错误,会导致调用方 panic 或死锁。
- mock writer 必须同时实现
io.Writer和io.Closer,Close()返回nil - 如果原逻辑用了
bufio.NewWriter(os.Stdout),记得在测试前调用Flush(),否则错误卡在缓冲区不暴露 - 禁止用
os.Stdout = myMock直接赋值——破坏并发安全;应通过参数注入或配置项切换 - 真正磁盘满时,
Write()返回短写 +nil错误,之后第一次Close()才返回ENOSPC;mock 要复现这个两阶段行为
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











