{"id":"1bade448-1b31-49b7-ad12-75542a5d27a7","shortId":"2eEZXX","kind":"skill","title":"dotnet-testing-filesystem-testing-abstractions","tagline":"使用 System.IO.Abstractions 測試檔案系統操作的專門技能。當需要測試 File、Directory、Path 等操作、模擬檔案系統時使用。涵蓋 IFileSystem、MockFileSystem、檔案讀寫測試、目錄操作測試等。\nMake sure to use this skill whenever the user mentions file system testing, IFileSystem, MockFileSystem, System.IO.Abstractions, or testing file/directory","description":"# 檔案系統測試：使用 System.IO.Abstractions 模擬檔案操作\n\n## 核心原則\n\n### 1. 檔案系統相依性的根本問題\n\n傳統直接使用 `System.IO` 靜態類別的程式碼難以測試，原因包括：\n\n- **速度問題**：實際磁碟 IO 比記憶體操作慢 10-100 倍\n- **環境相依**：測試結果受檔案系統狀態、權限、路徑影響\n- **副作用**：測試會在磁碟上留下痕跡，影響其他測試\n- **並行問題**：多個測試同時操作同一檔案會產生競爭條件\n- **錯誤模擬困難**：難以模擬權限不足、磁碟空間不足等異常\n\n### 2. System.IO.Abstractions 解決方案\n\n將 System.IO 靜態類別包裝成介面的套件，支援依賴注入和測試替身。\n\n**必要 NuGet 套件**：\n\n```xml\n<!-- 正式環境 -->\n<PackageReference Include=\"System.IO.Abstractions\" Version=\"22.1.0\" />\n\n<!-- 測試專案 -->\n<PackageReference Include=\"System.IO.Abstractions.TestingHelpers\" Version=\"22.1.0\" />\n```\n\n### 3. 重構步驟\n\n**步驟一**：將直接使用靜態類別的程式碼改為依賴 `IFileSystem`\n\n```csharp\n// ❌ 重構前（不可測試）\npublic class ConfigService\n{\n    public string LoadConfig(string path) => File.ReadAllText(path);\n}\n\n// ✅ 重構後（可測試）\npublic class ConfigService\n{\n    private readonly IFileSystem _fileSystem;\n    public ConfigService(IFileSystem fileSystem) => _fileSystem = fileSystem;\n    public string LoadConfig(string path) => _fileSystem.File.ReadAllText(path);\n}\n```\n\n**步驟二**：在 DI 容器中註冊真實實作\n\n```csharp\nservices.AddSingleton<IFileSystem, FileSystem>();\n```\n\n**步驟三**：在測試中使用 MockFileSystem\n\n```csharp\nvar mockFs = new MockFileSystem(new Dictionary<string, MockFileData>\n{\n    [\"config.json\"] = new MockFileData(\"{ \\\"key\\\": \\\"value\\\" }\")\n});\nvar service = new ConfigService(mockFs);\n```\n\n## MockFileSystem 測試模式\n\n涵蓋四種核心測試模式：預設檔案狀態建立、驗證寫入結果、目錄操作測試、使用 NSubstitute 模擬 IO 異常（UnauthorizedAccessException 等）。另含進階技巧：串流操作測試、檔案資訊查詢測試、備份檔案測試。\n\n> 完整 MockFileSystem 測試模式與進階技巧請參考 [references/mockfilesystem-patterns.md](references/mockfilesystem-patterns.md)\n\n## 最佳實踐\n\n### 應該這樣做\n\n1. **使用 Path.Combine 處理路徑** — `_fileSystem.Path.Combine(\"configs\", \"app.json\")`\n2. **防禦性檢查檔案存在性** — 在讀取前先檢查 `_fileSystem.File.Exists()`\n3. **自動建立必要目錄** — 寫入前確保目錄存在\n4. **妥善處理各種 IO 異常** — UnauthorizedAccessException、IOException、DirectoryNotFoundException\n5. **每個測試使用獨立的 MockFileSystem** — 確保測試隔離\n\n### 應該避免\n\n1. **硬編碼路徑分隔符號** — 使用 `Path.Combine` 取代 `\\\\` 或 `/`\n2. **在單元測試中使用真實檔案系統** — 使用 MockFileSystem\n3. **忽略例外處理** — 不要假設檔案一定存在\n\n## 效能考量\n\n- **MockFileSystem 速度**：比真實檔案操作快 10-100 倍\n- **記憶體使用**：只建立測試必需的檔案，避免模擬超大檔案\n\n## 實務整合範例\n\n請參考 `templates/` 目錄下的完整實作：\n\n- `configmanager-service.cs` - 設定檔管理服務（載入/儲存/備份）\n- `filemanager-service.cs` - 檔案管理服務（複製/目錄操作/錯誤處理）\n\n## 輸出格式\n\n- 產生使用 IFileSystem 介面的服務類別\n- 產生使用 MockFileSystem 的測試類別\n- 包含檔案讀寫、目錄操作、路徑處理測試範例\n- 提供 .csproj 套件參考（System.IO.Abstractions、System.IO.Abstractions.TestingHelpers）\n\n## 參考資源\n\n### 原始文章\n\n本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章：\n\n- **Day 17 - 檔案與 IO 測試：使用 System.IO.Abstractions 模擬檔案系統**\n  - 鐵人賽文章：https://ithelp.ithome.com.tw/articles/10375981\n  - 範例程式碼：https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day17\n\n### 官方文件\n\n- [System.IO.Abstractions GitHub](https://github.com/TestableIO/System.IO.Abstractions)\n- [System.IO.Abstractions NuGet](https://www.nuget.org/packages/System.IO.Abstractions/)\n- [TestingHelpers NuGet](https://www.nuget.org/packages/System.IO.Abstractions.TestingHelpers/)\n\n### 相關技能\n\n- `nsubstitute-mocking` - 測試替身與模擬\n- `unit-test-fundamentals` - 單元測試基礎","tags":["dotnet","testing","filesystem","abstractions","agent","skills","kevintsengtw","agent-skills","ai-assisted-development","copilot-skills","csharp","dotnet-testing"],"capabilities":["skill","source-kevintsengtw","skill-dotnet-testing-filesystem-testing-abstractions","topic-agent-skills","topic-ai-assisted-development","topic-copilot-skills","topic-csharp","topic-dotnet","topic-dotnet-testing","topic-github-copilot","topic-integration-testing","topic-testing","topic-unit-testing","topic-xunit"],"categories":["dotnet-testing-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-filesystem-testing-abstractions","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add kevintsengtw/dotnet-testing-agent-skills","source_repo":"https://github.com/kevintsengtw/dotnet-testing-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 23 github stars · SKILL.md body (3,029 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T13:02:26.924Z","embedding":null,"createdAt":"2026-04-18T23:04:30.251Z","updatedAt":"2026-04-24T13:02:26.924Z","lastSeenAt":"2026-04-24T13:02:26.924Z","tsv":"'-100':56,219 '/articles/10375981':271 '/kevintsengtw/30days_in_testing_samples/tree/main/day17':275 '/packages/system.io.abstractions.testinghelpers/)':291 '/packages/system.io.abstractions/)':286 '/testableio/system.io.abstractions)':281 '1':45,175,201 '10':55,218 '17':261 '2':70,182,207 '3':81,186,211 '30':257 '4':189 '5':196 'abstract':6 'app.json':181 'class':90,102 'config':180 'config.json':141 'configmanager-service.cs':228 'configservic':91,103,109,149 'csharp':86,125,132 'csproj':249 'day':260 'di':123 'dictionari':138 'directori':12 'directorynotfoundexcept':195 'dotnet':2 'dotnet-testing-filesystem-testing-abstract':1 'file':11,31 'file.readalltext':97 'file/directory':39 'filemanager-service.cs':233 'filesystem':4,107,111,112,113,128 'filesystem.file.exists':185 'filesystem.file.readalltext':119 'filesystem.path.combine':179 'fundament':300 'github':278 'github.com':274,280 'github.com/kevintsengtw/30days_in_testing_samples/tree/main/day17':273 'github.com/testableio/system.io.abstractions)':279 'ifilesystem':17,34,85,106,110,127,240 'io':53,160,191,263 'ioexcept':194 'ithelp.ithome.com.tw':270 'ithelp.ithome.com.tw/articles/10375981':269 'key':144 'loadconfig':94,116 'make':21 'mention':30 'mock':295 'mockf':134,150 'mockfiledata':140,143 'mockfilesystem':18,35,131,136,151,169,198,210,215,243 'new':135,137,142,148 'nsubstitut':158,294 'nsubstitute-mock':293 'nuget':78,283,288 'path':13,96,98,118,120 'path.combine':177,204 'privat':104 'public':89,92,101,108,114 'readon':105 'references/mockfilesystem-patterns.md':171,172 'servic':147 'services.addsingleton':126 'skill':26 'skill-dotnet-testing-filesystem-testing-abstractions' 'source-kevintsengtw' 'string':93,95,115,117,139 'sure':22 'system':32 'system.io':48,74 'system.io.abstractions':8,36,42,71,251,266,277,282 'system.io.abstractions.testinghelpers':252 'templat':226 'test':3,5,33,38,299 'testinghelp':287 'topic-agent-skills' 'topic-ai-assisted-development' 'topic-copilot-skills' 'topic-csharp' 'topic-dotnet' 'topic-dotnet-testing' 'topic-github-copilot' 'topic-integration-testing' 'topic-testing' 'topic-unit-testing' 'topic-xunit' 'unauthorizedaccessexcept':162,193 'unit':298 'unit-test-fundament':297 'use':24 'user':29 'valu':145 'var':133,146 'whenev':27 'www.nuget.org':285,290 'www.nuget.org/packages/system.io.abstractions.testinghelpers/)':289 'www.nuget.org/packages/system.io.abstractions/)':284 'xml':80 '不可測試':88 '不要假設檔案一定存在':213 '並行問題':65 '串流操作測試':165 '介面的服務類別':241 '使用':7,41,157,176,203,209,265 '倍':57,220 '備份':232 '備份檔案測試':167 '傳統直接使用':47 '儲存':231 '副作用':62 '包含檔案讀寫':245 '原因包括':50 '原始文章':254 '參考資源':253 '取代':205 '另含進階技巧':164 '只建立測試必需的檔案':222 '可測試':100 '單元測試基礎':301 '在':122 '在單元測試中使用真實檔案系統':208 '在測試中使用':130 '在讀取前先檢查':184 '多個測試同時操作同一檔案會產生競爭條件':66 '天挑戰':258 '套件':79 '套件參考':250 '妥善處理各種':190 '完整':168 '官方文件':276 '容器中註冊真實實作':124 '實務整合範例':224 '實際磁碟':52 '寫入前確保目錄存在':188 '將':73 '將直接使用靜態類別的程式碼改為依賴':84 '影響其他測試':64 '必要':77 '忽略例外處理':212 '應該這樣做':174 '應該避免':200 '或':206 '提供':248 '支援依賴注入和測試替身':76 '效能考量':214 '最佳實踐':173 '本技能內容提煉自':255 '核心原則':44 '模擬':159 '模擬檔案操作':43 '模擬檔案系統':267 '模擬檔案系統時使用':15 '檔案管理服務':234 '檔案系統測試':40 '檔案系統相依性的根本問題':46 '檔案與':262 '檔案讀寫測試':19 '檔案資訊查詢測試':166 '權限':60 '步驟一':83 '步驟三':129 '步驟二':121 '每個測試使用獨立的':197 '比真實檔案操作快':217 '比記憶體操作慢':54 '涵蓋':16 '涵蓋四種核心測試模式':153 '測試':264 '測試替身與模擬':296 '測試會在磁碟上留下痕跡':63 '測試模式':152 '測試模式與進階技巧請參考':170 '測試檔案系統操作的專門技能':9 '測試結果受檔案系統狀態':59 '環境相依':58 '產生使用':239,242 '異常':161,192 '當需要測試':10 '的測試類別':244 '目錄下的完整實作':227 '目錄操作':236,246 '目錄操作測試':156 '目錄操作測試等':20 '相關技能':292 '硬編碼路徑分隔符號':202 '確保測試隔離':199 '磁碟空間不足等異常':69 '等':163 '等操作':14 '範例程式碼':272 '系列文章':259 '老派軟體工程師的測試修練':256 '自動建立必要目錄':187 '處理路徑':178 '複製':235 '解決方案':72 '記憶體使用':221 '設定檔管理服務':229 '請參考':225 '路徑影響':61 '路徑處理測試範例':247 '載入':230 '輸出格式':238 '速度':216 '速度問題':51 '避免模擬超大檔案':223 '重構前':87 '重構後':99 '重構步驟':82 '錯誤模擬困難':67 '錯誤處理':237 '鐵人賽文章':268 '防禦性檢查檔案存在性':183 '難以模擬權限不足':68 '靜態類別包裝成介面的套件':75 '靜態類別的程式碼難以測試':49 '預設檔案狀態建立':154 '驗證寫入結果':155","prices":[{"id":"6268c073-dd37-4037-b9d4-fa57a1931457","listingId":"1bade448-1b31-49b7-ad12-75542a5d27a7","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"kevintsengtw","category":"dotnet-testing-agent-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T23:04:30.251Z"}],"sources":[{"listingId":"1bade448-1b31-49b7-ad12-75542a5d27a7","source":"github","sourceId":"kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-filesystem-testing-abstractions","sourceUrl":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-filesystem-testing-abstractions","isPrimary":false,"firstSeenAt":"2026-04-18T23:04:30.251Z","lastSeenAt":"2026-04-24T13:02:26.924Z"}],"details":{"listingId":"1bade448-1b31-49b7-ad12-75542a5d27a7","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"kevintsengtw","slug":"dotnet-testing-filesystem-testing-abstractions","github":{"repo":"kevintsengtw/dotnet-testing-agent-skills","stars":23,"topics":["agent-skills","ai-assisted-development","copilot-skills","csharp","dotnet","dotnet-testing","github-copilot","integration-testing","testing","unit-testing","xunit"],"license":"mit","html_url":"https://github.com/kevintsengtw/dotnet-testing-agent-skills","pushed_at":"2026-03-31T07:28:56Z","description":"AI Agent Skills for .NET Testing - Based on 30-Day Testing Challenge (iThome Ironman 2025 Winner)","skill_md_sha":"b9a07e32ac7edff1181a6e29b724c62b2ad5b820","skill_md_path":"skills/dotnet-testing-filesystem-testing-abstractions/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-filesystem-testing-abstractions"},"layout":"multi","source":"github","category":"dotnet-testing-agent-skills","frontmatter":{"name":"dotnet-testing-filesystem-testing-abstractions","description":"使用 System.IO.Abstractions 測試檔案系統操作的專門技能。當需要測試 File、Directory、Path 等操作、模擬檔案系統時使用。涵蓋 IFileSystem、MockFileSystem、檔案讀寫測試、目錄操作測試等。\nMake sure to use this skill whenever the user mentions file system testing, IFileSystem, MockFileSystem, System.IO.Abstractions, or testing file/directory operations, even if they don't explicitly ask for filesystem testing guidance.\nKeywords: file testing, filesystem, 檔案測試, 檔案系統測試, IFileSystem, MockFileSystem, System.IO.Abstractions, File.ReadAllText, File.WriteAllText, Directory.CreateDirectory, Path.Combine, mock file system, 檔案抽象化"},"skills_sh_url":"https://skills.sh/kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-filesystem-testing-abstractions"},"updatedAt":"2026-04-24T13:02:26.924Z"}}