应使用 System.IO.Abstractions 封装文件操作,业务类依赖 IFileSystem 接口,生产用 FileSystem,测试用 MemoryFileSystem(轻量但行为有差异)或 StagedFileSystem(真实磁盘隔离),路径必须通过 fileSystem.Path.Combine 等动态生成,禁止硬编码。

用 System.IO.Abstractions 替换原生 System.IO 调用
直接硬编码 File.ReadAllText、Directory.GetFiles 等调用,测试时就只能 mock 静态方法(难、不稳定)或依赖真实磁盘(慢、不隔离)。必须把所有文件操作抽象成接口实例。
推荐用开源库 System.IO.Abstractions —— 它不是“模拟器”,而是对 .NET 文件 API 的完整接口封装,天然支持注入和替换。
- 安装 NuGet 包:
System.IO.Abstractions(注意:不是System.IO.Abstractions.TestingHelpers,后者只是辅助) - 把业务类构造函数参数从
string path改为IFileSystem fileSystem - 生产代码中传入
new FileSystem();测试中传入new MemoryFileSystem()或自定义实现 - 所有路径操作(如
fileSystem.Path.Combine)必须通过IFileSystem实例调用,不能用Path.Combine
MemoryFileSystem 为什么不能直接当“内存盘”用
MemoryFileSystem 是 System.IO.Abstractions.TestingHelpers 提供的轻量实现,适合单元测试,但行为和真实文件系统有关键差异:
- 不支持符号链接、硬链接、文件锁、最后访问时间等元数据
- 路径大小写敏感性取决于运行平台(Windows 下默认不敏感,
MemoryFileSystem始终敏感) - 没有权限模型 ——
fileSystem.File.Exists("foo.txt")返回false不代表“没权限”,只代表“不存在” - 所有内容存在内存里,进程退出即清空;不支持跨线程并发写入(无内部锁)
如果测试涉及权限判断、时间戳校验、长路径或 UNC 格式,别用 MemoryFileSystem,改用 StagedFileSystem(见下一条)或真实临时目录 + 清理逻辑。
需要真实行为?用 StagedFileSystem 搭配临时目录
当测试必须验证异常路径、权限拒绝、只读文件、长文件名截断等场景时,MemoryFileSystem 会绕过这些逻辑。此时应让测试跑在真实文件系统上,但隔离、可销毁。
- 创建临时目录:
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) - 初始化
StagedFileSystem(来自System.IO.Abstractions.TestingHelpers):new StagedFileSystem(tempRoot) - 它会在
tempRoot下建真实子目录,并拦截所有操作——读写都落盘,但路径被重定向 - 测试结束调用
fileSystem.Dispose(),它会自动递归删除tempRoot - 注意:
StagedFileSystem不是线程安全的,单测间必须隔离实例
路径拼接和斜杠处理最容易出错
Windows 用反斜杠 ,Linux/macOS 用正斜杠 /,而 IFileSystem.Path 的行为和当前运行环境一致 —— 这会导致跨平台测试失败。
- 永远不要手动拼接路径字符串:
"dir" + Path.DirectorySeparatorChar + "file.txt"是错的 - 必须统一用
fileSystem.Path.Combine("dir", "file.txt") - 测试中若预设路径字符串(如用于
Assert.Equal),应使用fileSystem.Path.GetFullPath("relative/path")标准化后再比较 -
fileSystem.Path.IsPathRooted("C:\a")在 Linux 上返回false,哪怕你传的是 Windows 风格路径 —— 因为它按当前 OS 解析
最常被忽略的是:测试用例里写死 "C:\test\data.txt",结果在 CI 的 Linux runner 上直接挂掉。路径必须由 IFileSystem 动态生成或标准化。










