$npx -y skills add kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-awesome-assertions-guide使用 AwesomeAssertions 進行流暢且可讀的測試斷言技能。當需要撰寫清晰的斷言、比對物件、驗證集合、處理複雜比對時使用。涵蓋 Should()、BeEquivalentTo()、Contain()、ThrowAsync() 等完整 API。 Make sure to use this skill whenever the user mentions assertions, Should(), BeEquivalentTo, fluent assertions, AwesomeAssertions, or wants more readabl
| 1 | # AwesomeAssertions 流暢斷言指南 |
| 2 | |
| 3 | 本技能提供使用 AwesomeAssertions 進行高品質測試斷言的完整指南,涵蓋基礎語法、進階技巧與最佳實踐。 |
| 4 | |
| 5 | ## 關於 AwesomeAssertions |
| 6 | |
| 7 | **AwesomeAssertions** 是 FluentAssertions 的社群分支版本,使用 **Apache 2.0** 授權,完全免費且無商業使用限制。 |
| 8 | |
| 9 | ### 核心特色 |
| 10 | |
| 11 | - **完全免費**:Apache 2.0 授權,適合商業專案使用 |
| 12 | - **流暢語法**:支援方法鏈結的自然語言風格 |
| 13 | - **豐富斷言**:涵蓋物件、集合、字串、數值、例外等各種類型 |
| 14 | - **優秀錯誤訊息**:提供詳細且易理解的失敗資訊 |
| 15 | - **高性能**:優化的實作確保測試執行效率 |
| 16 | - **可擴展**:支援自訂 Assertions 方法 |
| 17 | |
| 18 | ### 與 FluentAssertions 的關係 |
| 19 | |
| 20 | AwesomeAssertions 是 FluentAssertions 的社群 fork,主要差異: |
| 21 | |
| 22 | | 項目 | FluentAssertions | AwesomeAssertions | |
| 23 | | -------------- | ------------------ | ---------------------- | |
| 24 | | **授權** | 商業專案需付費 | Apache 2.0(完全免費) | |
| 25 | | **命名空間** | `FluentAssertions` | `AwesomeAssertions` | |
| 26 | | **API 相容性** | 原版 | 高度相容 | |
| 27 | | **社群支援** | 官方維護 | 社群維護 | |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## 安裝與設定 |
| 32 | |
| 33 | ### NuGet 套件安裝 |
| 34 | |
| 35 | ```bash |
| 36 | # .NET CLI |
| 37 | dotnet add package AwesomeAssertions |
| 38 | |
| 39 | # Package Manager Console |
| 40 | Install-Package AwesomeAssertions |
| 41 | ``` |
| 42 | |
| 43 | ### csproj 設定(推薦) |
| 44 | |
| 45 | ```xml |
| 46 | <ItemGroup> |
| 47 | <PackageReference Include="AwesomeAssertions" Version="9.4.0" PrivateAssets="all" /> |
| 48 | </ItemGroup> |
| 49 | ``` |
| 50 | |
| 51 | ### 命名空間引用 |
| 52 | |
| 53 | ```csharp |
| 54 | using AwesomeAssertions; |
| 55 | using Xunit; |
| 56 | ``` |
| 57 | |
| 58 | --- |
| 59 | |
| 60 | ## 核心 Assertions 語法 |
| 61 | |
| 62 | 所有 Assertions 皆以 `.Should()` 開始,搭配流暢方法鏈結。 |
| 63 | |
| 64 | | 類別 | 常用方法 | 說明 | |
| 65 | |------|----------|------| |
| 66 | | **物件** | `NotBeNull()`, `BeOfType<T>()`, `BeEquivalentTo()` | 空值、類型、相等性檢查 | |
| 67 | | **字串** | `Contain()`, `StartWith()`, `MatchRegex()`, `BeEquivalentTo()` | 內容、模式、忽略大小寫比對 | |
| 68 | | **數值** | `BeGreaterThan()`, `BeInRange()`, `BeApproximately()` | 比較、範圍、浮點精度 | |
| 69 | | **數值集合** | `EqualApproximately()`, `NotEqualApproximately()` | 數值集合近似比對(9.4+) | |
| 70 | | **集合** | `HaveCount()`, `Contain()`, `BeEquivalentTo()`, `AllSatisfy()` | 數量、內容、順序、條件 | |
| 71 | | **例外** | `Throw<T>()`, `NotThrow()`, `WithMessage()`, `WithInnerException()` | 例外類型、訊息、巢狀例外 | |
| 72 | | **非同步** | `ThrowAsync<T>()`, `CompleteWithinAsync()` | 非同步例外與完成驗證 | |
| 73 | |
| 74 | > 完整語法範例與程式碼請參閱 [references/core-assertions-syntax.md](references/core-assertions-syntax.md) |
| 75 | |
| 76 | --- |
| 77 | |
| 78 | ## 進階技巧:複雜物件比對 |
| 79 | |
| 80 | 使用 `BeEquivalentTo()` 搭配 `options` 進行深度物件比較: |
| 81 | |
| 82 | - **排除屬性**:`options.Excluding(u => u.Id)` — 排除自動生成欄位 |
| 83 | - **動態排除**:`options.Excluding(ctx => ctx.Path.EndsWith("At"))` — 按模式排除 |
| 84 | - **循環參考**:`options.IgnoringCyclicReferences().WithMaxRecursionDepth(10)` |
| 85 | |
| 86 | --- |
| 87 | |
| 88 | ## 進階技巧:自訂 Assertions 擴展 |
| 89 | |
| 90 | 建立領域特定擴展方法,如 `product.Should().BeValidProduct()`,以及可重用排除擴展如 `ExcludingAuditFields()`。 |
| 91 | |
| 92 | 參考 [templates/custom-assertions-template.cs](templates/custom-assertions-template.cs) 瞭解完整實作。 |
| 93 | |
| 94 | > 完整範例請參閱 [references/complex-object-assertions.md](references/complex-object-assertions.md) |
| 95 | |
| 96 | --- |
| 97 | |
| 98 | ## 效能最佳化策略 |
| 99 | |
| 100 | - **大量資料**:先用 `HaveCount()` 快速檢查數量,再抽樣驗證(避免全量 `BeEquivalentTo`) |
| 101 | - **選擇性比對**:使用匿名物件 + `ExcludingMissingMembers()` 只驗證關鍵屬性 |
| 102 | |
| 103 | ```csharp |
| 104 | // 選擇性屬性比對 — 只驗證關鍵欄位 |
| 105 | order.Should().BeEquivalentTo(new |
| 106 | { |
| 107 | CustomerId = 123, |
| 108 | TotalAmount = 999.99m, |
| 109 | Status = "Pending" |
| 110 | }, options => options.ExcludingMissingMembers()); |
| 111 | ``` |
| 112 | |
| 113 | --- |
| 114 | |
| 115 | ## 9.2-9.4 版本新功能 |
| 116 | |
| 117 | ### 數值集合近似比對 — `[Not]EqualApproximately`(9.4+) |
| 118 | |
| 119 | 針對 `INumber<T>` 數值集合,新增近似相等斷言(需 .NET 8+): |
| 120 | |
| 121 | ```csharp |
| 122 | // 驗證數值集合在容差範圍內近似相等 |
| 123 | var actual = new[] { 1.001f, 2.002f, 3.003f }; |
| 124 | actual.Should().EqualApproximately(new[] { 1f, 2f, 3f }, 0.01f); |
| 125 | |
| 126 | // 驗證集合不在容差範圍內近似相等 |
| 127 | actual.Should().NotEqualApproximately(new[] { 10f, 20f, 30f }, 0.01f); |
| 128 | ``` |
| 129 | |
| 130 | > **與 `BeApproximately()` 的差異**:`BeApproximately()` 用於單一數值,`EqualApproximately()` 用於數值集合,會逐一比對每個元素。當容差為 0 時,`EqualApproximately` 的行為等同於 `Equal`。 |
| 131 | |
| 132 | ### 集合格式化最大項目數設定(9.4+) |
| 133 | |
| 134 | 斷言失敗時,集合預設顯示前 32 個項目。現在可透過 `FormattingOptions` 自訂: |
| 135 | |
| 136 | ```csharp |
| 137 | // 在 AssertionScope 中設定集合顯示上限 |
| 138 | using var scope = new AssertionScope(); |
| 139 | scope.FormattingOptions.MaxItems = 100; // 預設為 32 |
| 140 | |
| 141 | largeCollection.Should().BeEquivalentTo(expected); |
| 142 | ``` |
| 143 | |
| 144 | 也可透過自訂 Formatter 覆寫: |
| 145 | |
| 146 | ```csharp |
| 147 | class LargeCollectionFormatter : EnumerableValueFormatter |
| 148 | { |
| 149 | protected override int MaxItems => 100; |
| 150 | public override bool CanHandle(object value) => value is IEnumerable<MyEntity>; |
| 151 | } |
| 152 | ``` |
| 153 | |
| 154 | ### 集合出現次數約束(9.2+) |
| 155 | |
| 156 | `Contain` 方法新增出現次數約束: |
| 157 | |
| 158 | ```csharp |
| 159 | var numbers = new[] { 1, 2, 2, 3, 3, 3 }; |
| 160 | numbers.Should().Contain(2, AtLeast.Once()); |
| 161 | numbers.Should().Contain(3, Exactly.Times(3)); |
| 162 | ``` |
| 163 | |
| 164 | ### Null 安全性改善 — `[return: NotNull]`(9.4+) |
| 165 | |
| 166 | 所有 `Should()` 和斷言方法加入 `[return: NotNull]` 標註,讓編譯器的 nullable 分析正確追蹤: |
| 167 | |
| 168 | ```csharp |
| 169 | // 9.4 前:Should() 後仍視為可能 null,需加 ! 運算子 |
| 170 | var user = GetUser(); |
| 171 | user.Should().NotBeNull(); |
| 172 | user!.Name.Should().Be("test"); // 需要 ! 消除警告 |
| 173 | |
| 174 | // 9.4 後:Should().NotBeNull() 返回帶 [NotNull] 標註,編譯器 |