$npx -y skills add kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-autodata-xunit-integrationAutoFixture 與 xUnit 整合完整指南。當需要使用 AutoData 或 InlineAutoData 簡化 xUnit 參數化測試資料準備時使用。涵蓋自訂 Customization 與測試資料屬性,提升測試可讀性與維護性。 Make sure to use this skill whenever the user mentions AutoData, InlineAutoData, MemberAutoData, AutoFixture xUnit integration, or parameterized test data attr
| 1 | # AutoData 屬性家族:xUnit 與 AutoFixture 的整合應用 |
| 2 | |
| 3 | ## 概述 |
| 4 | |
| 5 | AutoData 屬性家族是 `AutoFixture.Xunit2` 套件提供的功能,將 AutoFixture 的資料產生能力與 xUnit 的參數化測試整合,讓測試參數自動注入,大幅減少測試準備程式碼。 |
| 6 | |
| 7 | ### 核心特色 |
| 8 | |
| 9 | 1. **AutoData**:自動產生所有測試參數 |
| 10 | 2. **InlineAutoData**:混合固定值與自動產生 |
| 11 | 3. **MemberAutoData**:結合外部資料來源 |
| 12 | 4. **CompositeAutoData**:多重資料來源整合 |
| 13 | 5. **CollectionSizeAttribute**:控制集合產生數量 |
| 14 | |
| 15 | ## 安裝套件 |
| 16 | |
| 17 | ```xml |
| 18 | <PackageReference Include="AutoFixture" Version="4.18.1" /> |
| 19 | <PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" /> |
| 20 | ``` |
| 21 | |
| 22 | ```bash |
| 23 | dotnet add package AutoFixture.Xunit2 |
| 24 | ``` |
| 25 | |
| 26 | ## AutoData:完全自動產生參數 |
| 27 | |
| 28 | `AutoData` 是最基礎的屬性,自動為測試方法的所有參數產生測試資料。 |
| 29 | |
| 30 | ### 基本使用 |
| 31 | |
| 32 | ```csharp |
| 33 | using AutoFixture.Xunit2; |
| 34 | |
| 35 | public class Person |
| 36 | { |
| 37 | public Guid Id { get; set; } |
| 38 | |
| 39 | [StringLength(10)] |
| 40 | public string Name { get; set; } = string.Empty; |
| 41 | |
| 42 | [Range(18, 80)] |
| 43 | public int Age { get; set; } |
| 44 | |
| 45 | public string Email { get; set; } = string.Empty; |
| 46 | public DateTime CreateTime { get; set; } |
| 47 | } |
| 48 | |
| 49 | [Theory] |
| 50 | [AutoData] |
| 51 | public void AutoData_應能自動產生所有參數(Person person, string message, int count) |
| 52 | { |
| 53 | // Arrange & Act - 參數已由 AutoData 自動產生 |
| 54 | |
| 55 | // Assert |
| 56 | person.Should().NotBeNull(); |
| 57 | person.Id.Should().NotBe(Guid.Empty); |
| 58 | person.Name.Should().HaveLength(10); // 遵循 StringLength 限制 |
| 59 | person.Age.Should().BeInRange(18, 80); // 遵循 Range 限制 |
| 60 | message.Should().NotBeNullOrEmpty(); |
| 61 | count.Should().NotBe(0); |
| 62 | } |
| 63 | ``` |
| 64 | |
| 65 | ### 透過 DataAnnotation 約束參數 |
| 66 | |
| 67 | ```csharp |
| 68 | [Theory] |
| 69 | [AutoData] |
| 70 | public void AutoData_透過DataAnnotation約束參數( |
| 71 | [StringLength(5, MinimumLength = 3)] string shortName, |
| 72 | [Range(1, 100)] int percentage, |
| 73 | Person person) |
| 74 | { |
| 75 | // Assert |
| 76 | shortName.Length.Should().BeInRange(3, 5); |
| 77 | percentage.Should().BeInRange(1, 100); |
| 78 | person.Should().NotBeNull(); |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | ## InlineAutoData:混合固定值與自動產生 |
| 83 | |
| 84 | `InlineAutoData` 結合了 `InlineData` 的固定值特性與 `AutoData` 的自動產生功能。 |
| 85 | |
| 86 | ### 基本語法 |
| 87 | |
| 88 | ```csharp |
| 89 | [Theory] |
| 90 | [InlineAutoData("VIP客戶", 1000)] |
| 91 | [InlineAutoData("一般客戶", 500)] |
| 92 | [InlineAutoData("新客戶", 100)] |
| 93 | public void InlineAutoData_混合固定值與自動產生( |
| 94 | string customerType, // 對應第 1 個固定值 |
| 95 | decimal creditLimit, // 對應第 2 個固定值 |
| 96 | Person person) // 由 AutoFixture 產生 |
| 97 | { |
| 98 | // Arrange |
| 99 | var customer = new Customer |
| 100 | { |
| 101 | Person = person, |
| 102 | Type = customerType, |
| 103 | CreditLimit = creditLimit |
| 104 | }; |
| 105 | |
| 106 | // Assert |
| 107 | customer.Type.Should().Be(customerType); |
| 108 | customer.CreditLimit.Should().BeOneOf(1000, 500, 100); |
| 109 | customer.Person.Should().NotBeNull(); |
| 110 | } |
| 111 | ``` |
| 112 | |
| 113 | ### 參數順序一致性 |
| 114 | |
| 115 | 固定值的順序必須與方法參數順序一致: |
| 116 | |
| 117 | ```csharp |
| 118 | [Theory] |
| 119 | [InlineAutoData("產品A", 100)] // 依序對應 name, price |
| 120 | [InlineAutoData("產品B", 200)] |
| 121 | public void InlineAutoData_參數順序一致性( |
| 122 | string name, // 對應第 1 個固定值 |
| 123 | decimal price, // 對應第 2 個固定值 |
| 124 | string description, // 由 AutoFixture 產生 |
| 125 | Product product) // 由 AutoFixture 產生 |
| 126 | { |
| 127 | // Assert |
| 128 | name.Should().BeOneOf("產品A", "產品B"); |
| 129 | price.Should().BeOneOf(100, 200); |
| 130 | description.Should().NotBeNullOrEmpty(); |
| 131 | product.Should().NotBeNull(); |
| 132 | } |
| 133 | ``` |
| 134 | |
| 135 | ### 重要限制:只能使用編譯時常數 |
| 136 | |
| 137 | ```csharp |
| 138 | // 正確:使用常數 |
| 139 | [InlineAutoData("VIP", 100000)] |
| 140 | [InlineAutoData("Premium", 50000)] |
| 141 | |
| 142 | // 錯誤:不能使用變數 |
| 143 | private const decimal VipCreditLimit = 100000m; |
| 144 | [InlineAutoData("VIP", VipCreditLimit)] // 編譯錯誤 |
| 145 | |
| 146 | // 錯誤:不能使用運算式 |
| 147 | [InlineAutoData("VIP", 100 * 1000)] // 編譯錯誤 |
| 148 | ``` |
| 149 | |
| 150 | 需要使用複雜資料時,應使用 `MemberAutoData`。 |
| 151 | |
| 152 | ## MemberAutoData:結合外部資料來源 |
| 153 | |
| 154 | `MemberAutoData` 允許從類別的方法、屬性或欄位中獲取測試資料。 |
| 155 | |
| 156 | ### 使用靜態方法 |
| 157 | |
| 158 | ```csharp |
| 159 | public class MemberAutoDataTests |
| 160 | { |
| 161 | public static IEnumerable<object[]> GetProductCategories() |
| 162 | { |
| 163 | yield return new object[] { "3C產品", "TECH" }; |
| 164 | yield return new object[] { "服飾配件", "FASHION" }; |
| 165 | yield return new object[] { "居家生活", "HOME" }; |
| 166 | yield return new object[] { "運動健身", "SPORTS" }; |
| 167 | } |
| 168 | |
| 169 | [Theory] |
| 170 | [MemberAutoData(nameof(GetProductCategories))] |
| 171 | public void MemberAutoData_使用靜態方法資料( |
| 172 | string categoryName, // 來自 GetProductCategories |
| 173 | string categoryCode, // 來自 GetProductCategories |
| 174 | Product product) // 由 AutoFixture 產生 |
| 175 | { |
| 176 | // Arrange |
| 177 | var categorizedProduct = new CategorizedProduct |
| 178 | { |
| 179 | Product = product, |
| 180 | CategoryName = categoryName, |
| 181 | CategoryCode = categoryCode |
| 182 | }; |
| 183 | |
| 184 | // Assert |
| 185 | categorizedProduct.CategoryName.Should().Be(categoryName); |
| 186 | categorizedProduct.CategoryCode.Should().Be(categoryCode); |
| 187 | categorizedProduct.Product.Should().NotBeNull(); |