{"id":"e1d2334a-d3d3-40f6-89d8-a3d6dd10674c","shortId":"SSGqFN","kind":"skill","title":"dotnet-testing-complex-object-comparison","tagline":"處理複雜物件比對與深層驗證的專門技能。當需要比對深層物件、排除特定屬性、處理循環參照、驗證 DTO/Entity 時使用。涵蓋 BeEquivalentTo、Excluding、Including、自訂比對規則等。\nMake sure to use this skill whenever the user mentions deep object comparison, BeEquivalentTo, DTO comparison, Excluding properties, or complex object validation in tests, ","description":"# 複雜物件比對指南（Complex Object Comparison）\n\n## 核心使用場景\n\n### 1. 深層物件結構比對 (Object Graph Comparison)\n\n當需要比對包含多層巢狀屬性的複雜物件時：\n\n```csharp\n[Fact]\npublic void ComplexObject_深層結構比對_應完全相符()\n{\n    var expected = new Order\n    {\n        Id = 1,\n        Customer = new Customer\n        {\n            Name = \"John Doe\",\n            Address = new Address\n            {\n                Street = \"123 Main St\",\n                City = \"Seattle\",\n                ZipCode = \"98101\"\n            }\n        },\n        Items = new[]\n        {\n            new OrderItem { ProductName = \"Laptop\", Quantity = 1, Price = 999.99m },\n            new OrderItem { ProductName = \"Mouse\", Quantity = 2, Price = 29.99m }\n        }\n    };\n\n    var actual = orderService.GetOrder(1);\n\n    // 深層物件比對\n    actual.Should().BeEquivalentTo(expected);\n}\n```\n\n### 2. 循環參照處理 (Circular Reference Handling)\n\n處理物件之間存在循環參照的情況：\n\n```csharp\n[Fact]\npublic void TreeStructure_循環參照_應正確處理()\n{\n    // 建立具有父子雙向參照的樹狀結構\n    var parent = new TreeNode { Value = \"Root\" };\n    var child1 = new TreeNode { Value = \"Child1\", Parent = parent };\n    var child2 = new TreeNode { Value = \"Child2\", Parent = parent };\n    parent.Children = new[] { child1, child2 };\n\n    var actualTree = treeService.GetTree(\"Root\");\n\n    // 處理循環參照\n    actualTree.Should().BeEquivalentTo(parent, options =>\n        options.IgnoringCyclicReferences()\n               .WithMaxRecursionDepth(10)\n    );\n}\n```\n\n### 3-6. 進階比對模式\n\nAwesomeAssertions 還提供多種進階比對模式：動態欄位排除（排除時間戳記、自動生成欄位）、巢狀物件欄位排除、大量資料效能最佳化比對（選擇性屬性比對、抽樣驗證策略）、以及嚴格/寬鬆排序控制。\n\n> 完整程式碼範例請參閱 [references/detailed-comparison-patterns.md](references/detailed-comparison-patterns.md)\n\n## 比對選項速查表\n\n| 選項方法                     | 用途           | 適用場景                   |\n| ---------------------------- | -------------- | -------------------------- |\n| `Excluding(x => x.Property)` | 排除特定屬性   | 排除時間戳記、自動生成欄位 |\n| `Including(x => x.Property)` | 只包含特定屬性 | 關鍵屬性驗證               |\n| `IgnoringCyclicReferences()` | 忽略循環參照   | 樹狀結構、雙向關聯         |\n| `WithMaxRecursionDepth(n)`   | 限制遞迴深度   | 深層巢狀結構               |\n| `WithStrictOrdering()`       | 嚴格順序比對   | 陣列/集合順序重要時        |\n| `WithoutStrictOrdering()`    | 寬鬆順序比對   | 陣列/集合順序不重要時      |\n| `WithTracing()`              | 啟用追蹤       | 除錯複雜比對失敗           |\n\n## 常見比對模式與解決方案\n\n### 模式 1：Entity Framework 實體比對\n\n```csharp\n[Fact]\npublic void EFEntity_資料庫實體_應排除導航屬性()\n{\n    var expected = new Product { Id = 1, Name = \"Laptop\", Price = 999 };\n    var actual = dbContext.Products.Find(1);\n\n    actual.Should().BeEquivalentTo(expected, options =>\n        options.ExcludingMissingMembers()  // 排除 EF 追蹤屬性\n               .Excluding(p => p.CreatedAt)\n               .Excluding(p => p.UpdatedAt)\n    );\n}\n```\n\n### 模式 2：API Response 比對\n\n```csharp\n[Fact]\npublic void ApiResponse_JSON反序列化_應忽略額外欄位()\n{\n    var expected = new UserDto \n    { \n        Id = 1, \n        Username = \"john_doe\" \n    };\n\n    var response = await httpClient.GetAsync(\"/api/users/1\");\n    var actual = await response.Content.ReadFromJsonAsync<UserDto>();\n\n    actual.Should().BeEquivalentTo(expected, options =>\n        options.ExcludingMissingMembers()  // 忽略 API 額外欄位\n    );\n}\n```\n\n### 模式 3：測試資料建構器比對\n\n```csharp\n[Fact]\npublic void Builder_測試資料_應匹配預期結構()\n{\n    var expected = new OrderBuilder()\n        .WithId(1)\n        .WithCustomer(\"John Doe\")\n        .WithItems(3)\n        .Build();\n\n    var actual = orderService.CreateOrder(orderRequest);\n\n    actual.Should().BeEquivalentTo(expected, options =>\n        options.Excluding(o => o.OrderNumber)  // 系統生成\n               .Excluding(o => o.CreatedAt)\n    );\n}\n```\n\n## 錯誤訊息最佳化\n\n### 提供有意義的錯誤訊息\n\n```csharp\n[Fact]\npublic void Comparison_錯誤訊息_應清楚說明差異()\n{\n    var expected = new User { Name = \"John\", Age = 30 };\n    var actual = userService.GetUser(1);\n\n    // 使用 because 參數提供上下文\n    actual.Should().BeEquivalentTo(expected, options =>\n        options.Excluding(u => u.Id)\n               .Because(\"ID 是系統自動生成的，不應納入比對\")\n    );\n}\n```\n\n### 使用 AssertionScope 進行批次驗證\n\n```csharp\n[Fact]\npublic void MultipleComparisons_批次驗證_應一次顯示所有失敗()\n{\n    var users = userService.GetAllUsers();\n\n    using (new AssertionScope())\n    {\n        foreach (var user in users)\n        {\n            user.Id.Should().BeGreaterThan(0);\n            user.Name.Should().NotBeNullOrEmpty();\n            user.Email.Should().MatchRegex(@\"^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$\");\n        }\n    }\n    // 所有失敗會一起報告，而非遇到第一個失敗就停止\n}\n```\n\n## 與其他技能整合\n\n此技能可與以下技能組合使用：\n\n- **awesome-assertions-guide**: 基礎斷言語法與常用 API\n- **autofixture-data-generation**: 自動生成測試資料\n- **test-data-builder-pattern**: 建構複雜測試物件\n- **unit-test-fundamentals**: 單元測試基礎與 3A 模式\n\n## 最佳實踐建議\n\n### 推薦做法\n\n1. **優先使用屬性排除而非包含**：除非只需驗證少數屬性，否則使用 `Excluding` 更清楚\n2. **建立可重用的排除擴充方法**：避免在每個測試重複排除邏輯\n3. **為大量資料比對設定合理策略**：平衡效能與驗證完整性\n4. **使用 AssertionScope 進行批次驗證**：一次看到所有失敗原因\n5. **提供有意義的 because 說明**：幫助未來維護者理解測試意圖\n\n### 避免做法\n\n1. **避免過度依賴完整物件比對**：考慮只驗證關鍵屬性\n2. **避免忽略循環參照問題**：使用 `IgnoringCyclicReferences()` 明確處理\n3. **避免在每個測試重複排除邏輯**：提取為擴充方法\n4. **避免對大量資料做完整深度比對**：使用抽樣或關鍵屬性驗證\n\n## 疑難排解\n\n### Q1: BeEquivalentTo 效能很慢怎麼辦？\n\n**A:** 使用以下策略優化：\n\n- 使用 `Including` 只比對關鍵屬性\n- 對大量資料採用抽樣驗證\n- 使用 `WithMaxRecursionDepth` 限制遞迴深度\n- 考慮使用 `AssertKeyPropertiesOnly` 快速比對關鍵欄位\n\n### Q2: 如何處理 StackOverflowException？\n\n**A:** 通常由循環參照引起：\n\n```csharp\noptions.IgnoringCyclicReferences()\n       .WithMaxRecursionDepth(10)\n```\n\n### Q3: 如何排除所有時間相關欄位？\n\n**A:** 使用路徑模式匹配：\n\n```csharp\noptions.Excluding(ctx => ctx.Path.EndsWith(\"At\"))\n       .Excluding(ctx => ctx.Path.EndsWith(\"Time\"))\n       .Excluding(ctx => ctx.Path.Contains(\"Timestamp\"))\n```\n\n### Q4: 比對失敗但看不出差異？\n\n**A:** 啟用詳細追蹤：\n\n```csharp\noptions.WithTracing()  // 產生詳細的比對追蹤資訊\n```\n\n## 範本檔案參考\n\n本技能提供以下範本檔案：\n\n- `templates/comparison-patterns.cs`: 常見比對模式範例\n- `templates/exclusion-strategies.cs`: 欄位排除策略與擴充方法\n\n## 輸出格式\n\n- 產生使用 BeEquivalentTo 的深層物件比對斷言\n- 包含 Excluding/Including 屬性過濾設定\n- **務必提及循環參照處理**：即使使用者未明確問到，也應說明 `IgnoringCyclicReferences()` 和 `WithMaxRecursionDepth(n)` 的用法，因為深層巢狀物件經常會遇到循環參照問題\n- 包含 DTO/Entity 比對的完整測試程式碼\n\n## 參考資源\n\n### 原始文章\n\n本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章：\n\n- **Day 05 - AwesomeAssertions 進階技巧與複雜情境應用**\n  - 鐵人賽文章：https://ithelp.ithome.com.tw/articles/10374425\n  - 範例程式碼：https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day05\n\n### 官方文件\n\n- [AwesomeAssertions GitHub](https://github.com/AwesomeAssertions/AwesomeAssertions)\n- [AwesomeAssertions Documentation](https://awesomeassertions.org/)\n\n### 相關技能\n\n- `awesome-assertions-guide` - AwesomeAssertions 基礎與進階用法\n- `unit-test-fundamentals` - 單元測試基礎","tags":["dotnet","testing","complex","object","comparison","agent","skills","kevintsengtw","agent-skills","ai-assisted-development","copilot-skills","csharp"],"capabilities":["skill","source-kevintsengtw","skill-dotnet-testing-complex-object-comparison","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-complex-object-comparison","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 (6,443 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.743Z","embedding":null,"createdAt":"2026-04-18T23:04:28.727Z","updatedAt":"2026-04-24T13:02:26.743Z","lastSeenAt":"2026-04-24T13:02:26.743Z","tsv":"'-6':165 '/)':569 '/api/users/1':281 '/articles/10374425':554 '/awesomeassertions/awesomeassertions)':564 '/kevintsengtw/30days_in_testing_samples/tree/main/day05':558 '0':389 '05':548 '1':48,66,91,107,217,233,241,273,309,351,429,452 '10':163,490 '123':77 '2':100,112,257,397,435,455 '29.99':102 '3':164,295,314,438,460 '30':347,544 '3a':425 '4':398,441,463 '5':446 '98101':83 '999':237 '999.99':93 'actual':105,239,283,317,349 'actual.should':109,242,286,320,355 'actualtre':153 'actualtree.should':157 'address':73,75 'age':346 'api':258,292,408 'apirespons':265 'assert':405,573 'assertionscop':367,381,443 'assertkeypropertieson':480 'autofixtur':410 'autofixture-data-gener':409 'await':279,284 'awesom':404,572 'awesome-assertions-guid':403,571 'awesomeassert':167,549,560,565,575 'awesomeassertions.org':568 'awesomeassertions.org/)':567 'beequivalentto':15,32,110,158,243,287,321,356,468,523 'begreaterthan':388 'build':315 'builder':301,417 'child1':133,137,150 'child2':141,145,151 'circular':114 'citi':80 'comparison':6,31,34,46,52,337 'complex':4,38,44 'complexobject':58 'csharp':54,118,221,261,297,333,369,487,495,512 'ctx':497,501,505 'ctx.path.contains':506 'ctx.path.endswith':498,502 'custom':67,69 'data':411,416 'day':547 'dbcontext.products.find':240 'deep':29 'document':566 'doe':72,276,312 'dotnet':2 'dotnet-testing-complex-object-comparison':1 'dto':33 'dto/entity':12,538 'ef':248 'efent':225 'entiti':218 'exclud':16,35,185,250,253,328,433,500,504 'excluding/including':526 'expect':62,111,229,244,269,288,305,322,341,357 'fact':55,119,222,262,298,334,370 'foreach':382 'framework':219 'fundament':423,580 'generat':412 'github':561 'github.com':557,563 'github.com/awesomeassertions/awesomeassertions)':562 'github.com/kevintsengtw/30days_in_testing_samples/tree/main/day05':556 'graph':51 'guid':406,574 'handl':116 'httpclient.getasync':280 'id':65,232,272,363 'ignoringcyclicrefer':196,458,531 'includ':17,191,473 'item':84 'ithelp.ithome.com.tw':553 'ithelp.ithome.com.tw/articles/10374425':552 'john':71,275,311,345 'json反序列化':266 'laptop':89,235 'm':94,103 'main':78 'make':19 'matchregex':393 'mention':28 'mous':98 'multiplecomparison':373 'n':201,534 'name':70,234,344 'new':63,68,74,85,86,95,128,134,142,149,230,270,306,342,380 'notbenullorempti':391 'o':325,329 'o.createdat':330 'o.ordernumber':326 'object':5,30,39,45,50 'option':160,245,289,323,358 'options.excluding':324,359,496 'options.excludingmissingmembers':246,290 'options.ignoringcyclicreferences':161,488 'options.withtracing':513 'order':64 'orderbuild':307 'orderitem':87,96 'orderrequest':319 'orderservice.createorder':318 'orderservice.getorder':106 'p':251,254 'p.createdat':252 'p.updatedat':255 'parent':127,138,139,146,147,159 'parent.children':148 'pattern':418 'price':92,101,236 'product':231 'productnam':88,97 'properti':36 'public':56,120,223,263,299,335,371 'q1':467 'q2':482 'q3':491 'q4':508 'quantiti':90,99 'refer':115 'references/detailed-comparison-patterns.md':179,180 'respons':259,278 'response.content.readfromjsonasync':285 'root':131,155 'seattl':81 'skill':24 'skill-dotnet-testing-complex-object-comparison' 'source-kevintsengtw' 'st':79 'stackoverflowexcept':484 'street':76 'sure':20 'templates/comparison-patterns.cs':517 'templates/exclusion-strategies.cs':519 'test':3,42,415,422,579 'test-data-builder-pattern':414 'time':503 'timestamp':507 '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' 'treenod':129,135,143 'treeservice.gettree':154 'treestructur':122 'u':360 'u.id':361 'unit':421,578 'unit-test-fundament':420,577 'use':22,379 'user':27,343,377,384,386 'user.email.should':392 'user.id.should':387 'user.name.should':390 'userdto':271 'usernam':274 'userservice.getallusers':378 'userservice.getuser':350 'valid':40 'valu':130,136,144 'var':61,104,126,132,140,152,228,238,268,277,282,304,316,340,348,376,383 'void':57,121,224,264,300,336,372 'w':394,395,396 'whenev':25 'withcustom':310 'withid':308 'withitem':313 'withmaxrecursiondepth':162,200,477,489,533 'withoutstrictord':208 'withstrictord':204 'withtrac':212 'x':186,192 'x.property':187,193 'zipcod':82 '一次看到所有失敗原因':445 '不應納入比對':365 '也應說明':530 '以及嚴格':176 '使用':352,366,442,457,472,476 '使用以下策略優化':471 '使用抽樣或關鍵屬性驗證':465 '使用路徑模式匹配':494 '優先使用屬性排除而非包含':430 '動態欄位排除':169 '務必提及循環參照處理':528 '包含':525,537 '即使使用者未明確問到':529 '原始文章':541 '參數提供上下文':354 '參考資源':540 '只包含特定屬性':194 '只比對關鍵屬性':474 '否則使用':432 '和':532 '啟用詳細追蹤':511 '啟用追蹤':213 '單元測試基礎':581 '單元測試基礎與':424 '嚴格順序比對':205 '因為深層巢狀物件經常會遇到循環參照問題':536 '基礎斷言語法與常用':407 '基礎與進階用法':576 '大量資料效能最佳化比對':173 '天挑戰':545 '如何排除所有時間相關欄位':492 '如何處理':483 '完整程式碼範例請參閱':178 '官方文件':559 '實體比對':220 '寬鬆排序控制':177 '寬鬆順序比對':209 '對大量資料採用抽樣驗證':475 '屬性過濾設定':527 '巢狀物件欄位排除':172 '常見比對模式範例':518 '常見比對模式與解決方案':215 '幫助未來維護者理解測試意圖':450 '平衡效能與驗證完整性':440 '建構複雜測試物件':419 '建立具有父子雙向參照的樹狀結構':125 '建立可重用的排除擴充方法':436 '循環參照':123 '循環參照處理':113 '快速比對關鍵欄位':481 '忽略':291 '忽略循環參照':197 '應一次顯示所有失敗':375 '應匹配預期結構':303 '應完全相符':60 '應忽略額外欄位':267 '應排除導航屬性':227 '應正確處理':124 '應清楚說明差異':339 '所有失敗會一起報告':399 '批次驗證':374 '抽樣驗證策略':175 '排除':247 '排除時間戳記':170,189 '排除特定屬性':9,188 '推薦做法':428 '提供有意義的':447 '提供有意義的錯誤訊息':332 '提取為擴充方法':462 '效能很慢怎麼辦':469 '明確處理':459 '是系統自動生成的':364 '時使用':13 '更清楚':434 '最佳實踐建議':427 '本技能內容提煉自':542 '本技能提供以下範本檔案':516 '核心使用場景':47 '模式':216,256,294,426 '樹狀結構':198 '欄位排除策略與擴充方法':520 '此技能可與以下技能組合使用':402 '比對':260 '比對失敗但看不出差異':509 '比對的完整測試程式碼':539 '比對選項速查表':181 '涵蓋':14 '深層巢狀結構':203 '深層物件比對':108 '深層物件結構比對':49 '深層結構比對':59 '測試資料':302 '測試資料建構器比對':296 '為大量資料比對設定合理策略':439 '產生使用':522 '產生詳細的比對追蹤資訊':514 '用途':183 '當需要比對包含多層巢狀屬性的複雜物件時':53 '當需要比對深層物件':8 '疑難排解':466 '的深層物件比對斷言':524 '的用法':535 '相關技能':570 '範例程式碼':555 '範本檔案參考':515 '系列文章':546 '系統生成':327 '老派軟體工程師的測試修練':543 '考慮使用':479 '考慮只驗證關鍵屬性':454 '而非遇到第一個失敗就停止':400 '自動生成欄位':171,190 '自動生成測試資料':413 '自訂比對規則等':18 '與其他技能整合':401 '處理循環參照':10,156 '處理物件之間存在循環參照的情況':117 '處理複雜物件比對與深層驗證的專門技能':7 '複雜物件比對指南':43 '說明':449 '資料庫實體':226 '輸出格式':521 '追蹤屬性':249 '通常由循環參照引起':486 '進行批次驗證':368,444 '進階技巧與複雜情境應用':550 '進階比對模式':166 '適用場景':184 '選擇性屬性比對':174 '選項方法':182 '避免做法':451 '避免在每個測試重複排除邏輯':437,461 '避免對大量資料做完整深度比對':464 '避免忽略循環參照問題':456 '避免過度依賴完整物件比對':453 '還提供多種進階比對模式':168 '錯誤訊息':338 '錯誤訊息最佳化':331 '鐵人賽文章':551 '關鍵屬性驗證':195 '限制遞迴深度':202,478 '陣列':206,210 '除錯複雜比對失敗':214 '除非只需驗證少數屬性':431 '集合順序不重要時':211 '集合順序重要時':207 '雙向關聯':199 '額外欄位':293 '驗證':11","prices":[{"id":"434d7b32-21df-4612-aa5b-7a5cc6aa11c5","listingId":"e1d2334a-d3d3-40f6-89d8-a3d6dd10674c","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:28.727Z"}],"sources":[{"listingId":"e1d2334a-d3d3-40f6-89d8-a3d6dd10674c","source":"github","sourceId":"kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-complex-object-comparison","sourceUrl":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-complex-object-comparison","isPrimary":false,"firstSeenAt":"2026-04-18T23:04:28.727Z","lastSeenAt":"2026-04-24T13:02:26.743Z"}],"details":{"listingId":"e1d2334a-d3d3-40f6-89d8-a3d6dd10674c","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"kevintsengtw","slug":"dotnet-testing-complex-object-comparison","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":"4e183b974bec076e1c2f9dbceff94d9767f762bc","skill_md_path":"skills/dotnet-testing-complex-object-comparison/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/kevintsengtw/dotnet-testing-agent-skills/tree/main/skills/dotnet-testing-complex-object-comparison"},"layout":"multi","source":"github","category":"dotnet-testing-agent-skills","frontmatter":{"name":"dotnet-testing-complex-object-comparison","description":"處理複雜物件比對與深層驗證的專門技能。當需要比對深層物件、排除特定屬性、處理循環參照、驗證 DTO/Entity 時使用。涵蓋 BeEquivalentTo、Excluding、Including、自訂比對規則等。\nMake sure to use this skill whenever the user mentions deep object comparison, BeEquivalentTo, DTO comparison, Excluding properties, or complex object validation in tests, even if they don't explicitly ask for comparison guidance.\nKeywords: object comparison, 物件比對, deep comparison, 深層比對, BeEquivalentTo, DTO 比對, Entity 驗證, 排除屬性, 循環參照, Excluding, Including, ExcludingNestedObjects, RespectingRuntimeTypes, WithStrictOrdering, 忽略時間戳記, exclude timestamp"},"skills_sh_url":"https://skills.sh/kevintsengtw/dotnet-testing-agent-skills/dotnet-testing-complex-object-comparison"},"updatedAt":"2026-04-24T13:02:26.743Z"}}