$npx -y skills add kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-filesystem-testing-abstractions使用 System.IO.Abstractions 測試檔案系統操作的專門技能。當需要測試 File、Directory、Path 等操作、模擬檔案系統時使用。涵蓋 IFileSystem、MockFileSystem、檔案讀寫測試、目錄操作測試等。 Make sure to use this skill whenever the user mentions file system testing, IFileSystem, MockFileSystem, System.IO.Abstractions, or testing file/directory
| 1 | # 檔案系統測試:使用 System.IO.Abstractions 模擬檔案操作 |
| 2 | |
| 3 | ## 核心原則 |
| 4 | |
| 5 | ### 1. 檔案系統相依性的根本問題 |
| 6 | |
| 7 | 傳統直接使用 `System.IO` 靜態類別的程式碼難以測試,原因包括: |
| 8 | |
| 9 | - **速度問題**:實際磁碟 IO 比記憶體操作慢 10-100 倍 |
| 10 | - **環境相依**:測試結果受檔案系統狀態、權限、路徑影響 |
| 11 | - **副作用**:測試會在磁碟上留下痕跡,影響其他測試 |
| 12 | - **並行問題**:多個測試同時操作同一檔案會產生競爭條件 |
| 13 | - **錯誤模擬困難**:難以模擬權限不足、磁碟空間不足等異常 |
| 14 | |
| 15 | ### 2. System.IO.Abstractions 解決方案 |
| 16 | |
| 17 | 將 System.IO 靜態類別包裝成介面的套件,支援依賴注入和測試替身。 |
| 18 | |
| 19 | **必要 NuGet 套件**: |
| 20 | |
| 21 | ```xml |
| 22 | <!-- 正式環境 --> |
| 23 | <PackageReference Include="System.IO.Abstractions" Version="22.1.0" /> |
| 24 | |
| 25 | <!-- 測試專案 --> |
| 26 | <PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="22.1.0" /> |
| 27 | ``` |
| 28 | |
| 29 | ### 3. 重構步驟 |
| 30 | |
| 31 | **步驟一**:將直接使用靜態類別的程式碼改為依賴 `IFileSystem` |
| 32 | |
| 33 | ```csharp |
| 34 | // ❌ 重構前(不可測試) |
| 35 | public class ConfigService |
| 36 | { |
| 37 | public string LoadConfig(string path) => File.ReadAllText(path); |
| 38 | } |
| 39 | |
| 40 | // ✅ 重構後(可測試) |
| 41 | public class ConfigService |
| 42 | { |
| 43 | private readonly IFileSystem _fileSystem; |
| 44 | public ConfigService(IFileSystem fileSystem) => _fileSystem = fileSystem; |
| 45 | public string LoadConfig(string path) => _fileSystem.File.ReadAllText(path); |
| 46 | } |
| 47 | ``` |
| 48 | |
| 49 | **步驟二**:在 DI 容器中註冊真實實作 |
| 50 | |
| 51 | ```csharp |
| 52 | services.AddSingleton<IFileSystem, FileSystem>(); |
| 53 | ``` |
| 54 | |
| 55 | **步驟三**:在測試中使用 MockFileSystem |
| 56 | |
| 57 | ```csharp |
| 58 | var mockFs = new MockFileSystem(new Dictionary<string, MockFileData> |
| 59 | { |
| 60 | ["config.json"] = new MockFileData("{ \"key\": \"value\" }") |
| 61 | }); |
| 62 | var service = new ConfigService(mockFs); |
| 63 | ``` |
| 64 | |
| 65 | ## MockFileSystem 測試模式 |
| 66 | |
| 67 | 涵蓋四種核心測試模式:預設檔案狀態建立、驗證寫入結果、目錄操作測試、使用 NSubstitute 模擬 IO 異常(UnauthorizedAccessException 等)。另含進階技巧:串流操作測試、檔案資訊查詢測試、備份檔案測試。 |
| 68 | |
| 69 | > 完整 MockFileSystem 測試模式與進階技巧請參考 [references/mockfilesystem-patterns.md](references/mockfilesystem-patterns.md) |
| 70 | |
| 71 | ## 最佳實踐 |
| 72 | |
| 73 | ### 應該這樣做 |
| 74 | |
| 75 | 1. **使用 Path.Combine 處理路徑** — `_fileSystem.Path.Combine("configs", "app.json")` |
| 76 | 2. **防禦性檢查檔案存在性** — 在讀取前先檢查 `_fileSystem.File.Exists()` |
| 77 | 3. **自動建立必要目錄** — 寫入前確保目錄存在 |
| 78 | 4. **妥善處理各種 IO 異常** — UnauthorizedAccessException、IOException、DirectoryNotFoundException |
| 79 | 5. **每個測試使用獨立的 MockFileSystem** — 確保測試隔離 |
| 80 | |
| 81 | ### 應該避免 |
| 82 | |
| 83 | 1. **硬編碼路徑分隔符號** — 使用 `Path.Combine` 取代 `\\` 或 `/` |
| 84 | 2. **在單元測試中使用真實檔案系統** — 使用 MockFileSystem |
| 85 | 3. **忽略例外處理** — 不要假設檔案一定存在 |
| 86 | |
| 87 | ## 效能考量 |
| 88 | |
| 89 | - **MockFileSystem 速度**:比真實檔案操作快 10-100 倍 |
| 90 | - **記憶體使用**:只建立測試必需的檔案,避免模擬超大檔案 |
| 91 | |
| 92 | ## 實務整合範例 |
| 93 | |
| 94 | 請參考 `templates/` 目錄下的完整實作: |
| 95 | |
| 96 | - `configmanager-service.cs` - 設定檔管理服務(載入/儲存/備份) |
| 97 | - `filemanager-service.cs` - 檔案管理服務(複製/目錄操作/錯誤處理) |
| 98 | |
| 99 | ## 輸出格式 |
| 100 | |
| 101 | - 產生使用 IFileSystem 介面的服務類別 |
| 102 | - 產生使用 MockFileSystem 的測試類別 |
| 103 | - 包含檔案讀寫、目錄操作、路徑處理測試範例 |
| 104 | - 提供 .csproj 套件參考(System.IO.Abstractions、System.IO.Abstractions.TestingHelpers) |
| 105 | |
| 106 | ## 參考資源 |
| 107 | |
| 108 | ### 原始文章 |
| 109 | |
| 110 | 本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章: |
| 111 | |
| 112 | - **Day 17 - 檔案與 IO 測試:使用 System.IO.Abstractions 模擬檔案系統** |
| 113 | - 鐵人賽文章:https://ithelp.ithome.com.tw/articles/10375981 |
| 114 | - 範例程式碼:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day17 |
| 115 | |
| 116 | ### 官方文件 |
| 117 | |
| 118 | - [System.IO.Abstractions GitHub](https://github.com/TestableIO/System.IO.Abstractions) |
| 119 | - [System.IO.Abstractions NuGet](https://www.nuget.org/packages/System.IO.Abstractions/) |
| 120 | - [TestingHelpers NuGet](https://www.nuget.org/packages/System.IO.Abstractions.TestingHelpers/) |
| 121 | |
| 122 | ### 相關技能 |
| 123 | |
| 124 | - `nsubstitute-mocking` - 測試替身與模擬 |
| 125 | - `unit-test-fundamentals` - 單元測試基礎 |