home / skills / kevintsengtw / dotnet-testing-agent-skills / dotnet-testing-autodata-xunit-integration

dotnet-testing-autodata-xunit-integration skill

/skills/dotnet-testing-autodata-xunit-integration

This skill helps you streamline xUnit parameterized tests with AutoFixture by auto-generating data and composing custom AutoData attributes.

npx playbooks add skill kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-autodata-xunit-integration

Review the files below or copy the command above to add this skill to your agents.

Files (7)
SKILL.md
14.2 KB
---
name: dotnet-testing-autodata-xunit-integration
description: |
  AutoFixture 與 xUnit 整合完整指南。當需要使用 AutoData 或 InlineAutoData 簡化 xUnit 參數化測試資料準備時使用。涵蓋自訂 Customization 與測試資料屬性,提升測試可讀性與維護性。
  Keywords: AutoData, InlineAutoData, AutoFixture xUnit, [AutoData], [InlineAutoData], AutoDataAttribute, ICustomization, DataAttribute, 參數化測試, Theory AutoData, MemberAutoData, 測試資料屬性, fixture.Customize
license: MIT
metadata:
  author: Kevin Tseng
  version: "1.0.0"
  tags: "autofixture, xunit, autodata, theory, parameterized-tests, customization"
  related_skills: "autofixture-basics, autofixture-customization, autofixture-nsubstitute-integration"
---

# AutoData 屬性家族:xUnit 與 AutoFixture 的整合應用

## 適用情境

- AutoData
- InlineAutoData
- MemberAutoData
- CompositeAutoData
- xUnit AutoFixture 整合
- 參數化測試資料
- 測試參數注入
- CollectionSizeAttribute
- 外部測試資料
- CSV 測試資料
- JSON 測試資料

## 概述

AutoData 屬性家族是 `AutoFixture.Xunit2` 套件提供的功能,將 AutoFixture 的資料產生能力與 xUnit 的參數化測試整合,讓測試參數自動注入,大幅減少測試準備程式碼。

### 核心特色

1. **AutoData**:自動產生所有測試參數
2. **InlineAutoData**:混合固定值與自動產生
3. **MemberAutoData**:結合外部資料來源
4. **CompositeAutoData**:多重資料來源整合
5. **CollectionSizeAttribute**:控制集合產生數量

## 安裝套件

```xml
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
```

```bash
dotnet add package AutoFixture.Xunit2
```

## AutoData:完全自動產生參數

`AutoData` 是最基礎的屬性,自動為測試方法的所有參數產生測試資料。

### 基本使用

```csharp
using AutoFixture.Xunit2;

public class Person
{
    public Guid Id { get; set; }
    
    [StringLength(10)]
    public string Name { get; set; } = string.Empty;
    
    [Range(18, 80)]
    public int Age { get; set; }
    
    public string Email { get; set; } = string.Empty;
    public DateTime CreateTime { get; set; }
}

[Theory]
[AutoData]
public void AutoData_應能自動產生所有參數(Person person, string message, int count)
{
    // Arrange & Act - 參數已由 AutoData 自動產生

    // Assert
    person.Should().NotBeNull();
    person.Id.Should().NotBe(Guid.Empty);
    person.Name.Should().HaveLength(10);     // 遵循 StringLength 限制
    person.Age.Should().BeInRange(18, 80);   // 遵循 Range 限制
    message.Should().NotBeNullOrEmpty();
    count.Should().NotBe(0);
}
```

### 透過 DataAnnotation 約束參數

```csharp
[Theory]
[AutoData]
public void AutoData_透過DataAnnotation約束參數(
    [StringLength(5, MinimumLength = 3)] string shortName,
    [Range(1, 100)] int percentage,
    Person person)
{
    // Assert
    shortName.Length.Should().BeInRange(3, 5);
    percentage.Should().BeInRange(1, 100);
    person.Should().NotBeNull();
}
```

## InlineAutoData:混合固定值與自動產生

`InlineAutoData` 結合了 `InlineData` 的固定值特性與 `AutoData` 的自動產生功能。

### 基本語法

```csharp
[Theory]
[InlineAutoData("VIP客戶", 1000)]
[InlineAutoData("一般客戶", 500)]
[InlineAutoData("新客戶", 100)]
public void InlineAutoData_混合固定值與自動產生(
    string customerType,    // 對應第 1 個固定值
    decimal creditLimit,    // 對應第 2 個固定值
    Person person)          // 由 AutoFixture 產生
{
    // Arrange
    var customer = new Customer
    {
        Person = person,
        Type = customerType,
        CreditLimit = creditLimit
    };

    // Assert
    customer.Type.Should().Be(customerType);
    customer.CreditLimit.Should().BeOneOf(1000, 500, 100);
    customer.Person.Should().NotBeNull();
}
```

### 參數順序一致性

固定值的順序必須與方法參數順序一致:

```csharp
[Theory]
[InlineAutoData("產品A", 100)]  // 依序對應 name, price
[InlineAutoData("產品B", 200)]
public void InlineAutoData_參數順序一致性(
    string name,        // 對應第 1 個固定值
    decimal price,      // 對應第 2 個固定值
    string description, // 由 AutoFixture 產生
    Product product)    // 由 AutoFixture 產生
{
    // Assert
    name.Should().BeOneOf("產品A", "產品B");
    price.Should().BeOneOf(100, 200);
    description.Should().NotBeNullOrEmpty();
    product.Should().NotBeNull();
}
```

### ⚠️ 重要限制:只能使用編譯時常數

```csharp
// ✅ 正確:使用常數
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]

// ❌ 錯誤:不能使用變數
private const decimal VipCreditLimit = 100000m;
[InlineAutoData("VIP", VipCreditLimit)]  // 編譯錯誤

// ❌ 錯誤:不能使用運算式
[InlineAutoData("VIP", 100 * 1000)]  // 編譯錯誤
```

需要使用複雜資料時,應使用 `MemberAutoData`。

## MemberAutoData:結合外部資料來源

`MemberAutoData` 允許從類別的方法、屬性或欄位中獲取測試資料。

### 使用靜態方法

```csharp
public class MemberAutoDataTests
{
    public static IEnumerable<object[]> GetProductCategories()
    {
        yield return new object[] { "3C產品", "TECH" };
        yield return new object[] { "服飾配件", "FASHION" };
        yield return new object[] { "居家生活", "HOME" };
        yield return new object[] { "運動健身", "SPORTS" };
    }

    [Theory]
    [MemberAutoData(nameof(GetProductCategories))]
    public void MemberAutoData_使用靜態方法資料(
        string categoryName,   // 來自 GetProductCategories
        string categoryCode,   // 來自 GetProductCategories
        Product product)       // 由 AutoFixture 產生
    {
        // Arrange
        var categorizedProduct = new CategorizedProduct
        {
            Product = product,
            CategoryName = categoryName,
            CategoryCode = categoryCode
        };

        // Assert
        categorizedProduct.CategoryName.Should().Be(categoryName);
        categorizedProduct.CategoryCode.Should().Be(categoryCode);
        categorizedProduct.Product.Should().NotBeNull();
    }
}
```

### 使用靜態屬性

```csharp
public static IEnumerable<object[]> StatusTransitions => new[]
{
    new object[] { OrderStatus.Created, OrderStatus.Confirmed },
    new object[] { OrderStatus.Confirmed, OrderStatus.Shipped },
    new object[] { OrderStatus.Shipped, OrderStatus.Delivered },
    new object[] { OrderStatus.Delivered, OrderStatus.Completed }
};

[Theory]
[MemberAutoData(nameof(StatusTransitions))]
public void MemberAutoData_使用靜態屬性_訂單狀態轉換(
    OrderStatus fromStatus,
    OrderStatus toStatus,
    Order order)
{
    // Arrange
    order.Status = fromStatus;

    // Act
    order.TransitionTo(toStatus);

    // Assert
    order.Status.Should().Be(toStatus);
}
```

## 自訂 AutoData 屬性

建立專屬的 AutoData 配置:

### DomainAutoDataAttribute

```csharp
public class DomainAutoDataAttribute : AutoDataAttribute
{
    public DomainAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Person 的自訂規則
        fixture.Customize<Person>(composer => composer
            .With(p => p.Age, () => Random.Shared.Next(18, 65))
            .With(p => p.Email, () => $"user{Random.Shared.Next(1000)}@example.com")
            .With(p => p.Name, () => $"測試用戶{Random.Shared.Next(100)}"));

        // 設定 Product 的自訂規則
        fixture.Customize<Product>(composer => composer
            .With(p => p.Price, () => Random.Shared.Next(100, 10000))
            .With(p => p.IsAvailable, true)
            .With(p => p.Name, () => $"產品{Random.Shared.Next(1000)}"));

        return fixture;
    }
}
```

### BusinessAutoDataAttribute

```csharp
public class BusinessAutoDataAttribute : AutoDataAttribute
{
    public BusinessAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Order 的業務規則
        fixture.Customize<Order>(composer => composer
            .With(o => o.Status, OrderStatus.Created)
            .With(o => o.Amount, () => Random.Shared.Next(1000, 50000))
            .With(o => o.OrderNumber, () => $"ORD{DateTime.Now:yyyyMMdd}{Random.Shared.Next(1000):D4}"));

        return fixture;
    }
}
```

### 使用自訂 AutoData

```csharp
[Theory]
[DomainAutoData]
public void 使用DomainAutoData(Person person, Product product)
{
    person.Age.Should().BeInRange(18, 64);
    person.Email.Should().EndWith("@example.com");
    product.IsAvailable.Should().BeTrue();
}

[Theory]
[BusinessAutoData]
public void 使用BusinessAutoData(Order order)
{
    order.Status.Should().Be(OrderStatus.Created);
    order.Amount.Should().BeInRange(1000, 49999);
    order.OrderNumber.Should().StartWith("ORD");
}
```

## CompositeAutoData:多重資料來源整合

透過繼承 `AutoDataAttribute` 並以反射合併多個 Fixture 的 Customizations,組合多個自訂 AutoData 配置:

```csharp
// 使用方式 - 自動套用 DomainAutoData + BusinessAutoData 的所有設定
[Theory]
[CompositeAutoData(typeof(DomainAutoDataAttribute), typeof(BusinessAutoDataAttribute))]
public void CompositeAutoData_整合多重資料來源(
    Person person, Product product, Order order)
{
    person.Age.Should().BeInRange(18, 64);       // DomainAutoData 的設定
    product.IsAvailable.Should().BeTrue();        // DomainAutoData 的設定
    order.Status.Should().Be(OrderStatus.Created); // BusinessAutoData 的設定
}
```

## CollectionSizeAttribute:控制集合大小

AutoData 預設的集合大小是 3,可透過自訂 `CollectionSizeAttribute` 控制產生的集合元素數量。完整實作與使用方式請參考 [CollectionSizeAttribute 完整指南](references/collection-size-attribute.md)。

## 外部測試資料整合

整合 CSV、JSON 等外部資料來源與 MemberAutoData,同時保留 AutoFixture 自動產生其餘參數的能力。完整指南請參考 [外部測試資料整合](references/external-data-integration.md)。

## 資料來源設計模式

階層式資料組織與可重用資料集的設計模式,建立 `BaseTestData` 基底類別統一管理測試資料路徑。完整範例請參考 [資料來源設計模式](references/data-source-patterns.md)。

## 與 Awesome Assertions 協作

```csharp
[Theory]
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]
[InlineAutoData("Regular", 20000)]
public void AutoData與AwesomeAssertions協作_客戶等級驗證(
    string customerLevel,
    decimal expectedCreditLimit,
    [Range(1000, 15000)] decimal orderAmount,
    Customer customer,
    Order order)
{
    // Arrange
    customer.Type = customerLevel;
    customer.CreditLimit = expectedCreditLimit;
    order.Amount = orderAmount;

    // Act
    var canPlaceOrder = customer.CanPlaceOrder(order.Amount);
    var discountRate = CalculateDiscount(customer.Type, order.Amount);

    // Assert - 使用 Awesome Assertions 語法
    customer.Type.Should().Be(customerLevel);
    customer.CreditLimit.Should().Be(expectedCreditLimit);
    customer.CreditLimit.Should().BePositive();
    
    order.Amount.Should().BeInRange(1000m, 15000m);
    canPlaceOrder.Should().BeTrue();
    discountRate.Should().BeInRange(0m, 0.3m);
}

private static decimal CalculateDiscount(string customerType, decimal orderAmount)
{
    var baseDiscount = customerType switch
    {
        "VIP" => 0.15m,
        "Premium" => 0.10m,
        "Regular" => 0.05m,
        _ => 0m
    };

    var largeOrderBonus = orderAmount > 30000m ? 0.05m : 0m;
    return Math.Min(baseDiscount + largeOrderBonus, 0.3m);
}
```

## 最佳實踐

### 應該做

1. **善用 DataAnnotation**
   - 在參數上使用 `[StringLength]`、`[Range]` 約束資料範圍
   - 確保產生的資料符合業務規則

2. **建立可重用的自訂 AutoData**
   - 為不同領域建立專屬的 AutoData 屬性
   - 集中管理測試資料的產生規則

3. **使用 MemberAutoData 處理複雜資料**
   - 當 InlineAutoData 無法滿足需求時使用
   - 支援變數、運算式和外部資料來源

4. **組織測試資料來源**
   - 建立階層式的資料來源結構
   - 將相關資料集中管理

### 應該避免

1. **在 InlineAutoData 使用非常數值**
   - InlineAutoData 只接受編譯時常數
   - 需要動態值時改用 MemberAutoData

2. **過度複雜的 CompositeAutoData**
   - 避免組合過多的 AutoData 來源
   - 保持配置的可理解性

3. **忽略參數順序**
   - InlineAutoData 的固定值必須與參數順序一致
   - 錯誤的順序會導致型別不符

## 程式碼範本

請參考 [templates](./templates) 資料夾中的範例檔案:

- [autodata-attributes.cs](./templates/autodata-attributes.cs) - AutoData 屬性家族使用範例
- [external-data-integration.cs](./templates/external-data-integration.cs) - CSV/JSON 外部資料整合
- [advanced-patterns.cs](./templates/advanced-patterns.cs) - 進階模式與 CollectionSizeAttribute

## 與其他技能的關係

- **autofixture-basics**:本技能的前置知識
- **autofixture-customization**:自訂化策略可用於 AutoData 屬性
- **autofixture-nsubstitute-integration**:下一步學習目標
- **awesome-assertions-guide**:搭配使用提升測試可讀性

## 參考資源

### 原始文章

本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:

- **Day 12 - 結合 AutoData:xUnit 與 AutoFixture 的整合應用**
  - 鐵人賽文章:https://ithelp.ithome.com.tw/articles/10375296
  - 範例程式碼:https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day12

### 官方文件

- [AutoFixture Cheat Sheet - AutoData Theories](https://github.com/AutoFixture/AutoFixture/wiki/Cheat-Sheet#autodata-theories)
- [AutoDataAttribute API Reference](https://autofixture.io/api/AutoFixture.Xunit.AutoDataAttribute.html)
- [InlineAutoDataAttribute API Reference](https://autofixture.io/api/AutoFixture.Xunit.InlineAutoDataAttribute.html)
- [CsvHelper 官方文件](https://joshclose.github.io/CsvHelper/)

Overview

This skill is a practical guide for integrating AutoFixture with xUnit using the AutoData attribute family. It explains AutoData, InlineAutoData, MemberAutoData, CompositeAutoData, and CollectionSizeAttribute to simplify parameterized test data setup. The content emphasizes custom AutoData attributes and fixture.Customize patterns to keep tests readable and maintainable.

How this skill works

AutoData attributes let AutoFixture generate and inject test values directly into xUnit Theory parameters, removing manual test setup. InlineAutoData mixes compile-time constants with generated values; MemberAutoData pulls rows from static members; CompositeAutoData merges multiple customizations. Custom AutoData attributes encapsulate fixture configuration, and CollectionSizeAttribute controls generated collection lengths.

When to use it

  • When you want to eliminate repetitive Arrange code by auto-generating method parameters.
  • When a test needs a mix of fixed inputs and generated complex objects (use InlineAutoData).
  • When test inputs come from external or computed sources (use MemberAutoData).
  • When domain-specific defaults or business rules should apply to generated objects (create custom AutoData).
  • When you need multiple fixture configurations combined for a single test (use CompositeAutoData).

Best practices

  • Annotate parameters with DataAnnotation attributes (StringLength, Range) to constrain generated values.
  • Create reusable custom AutoData attributes for domain or business rules to centralize test data logic.
  • Use MemberAutoData for non-constant or external data sources like CSV/JSON and for computed values.
  • Keep CompositeAutoData simple; avoid combining too many different customizations to preserve clarity.
  • Respect parameter order when using InlineAutoData: fixed values map to method parameters by position.

Example use cases

  • Quickly generate valid domain entities (Person, Product, Order) with domain-specific defaults for unit tests.
  • Mix a customer type constant with an AutoFixture-generated Customer object to test behavior variations.
  • Supply scenario rows from a static method or property while AutoFixture fills remaining parameters.
  • Combine DomainAutoData and BusinessAutoData to apply both domain defaults and business rules in one test.
  • Control list sizes produced by AutoFixture in collection-heavy tests using CollectionSizeAttribute.

FAQ

Can InlineAutoData accept variables or expressions?

No. InlineAutoData only accepts compile-time constants. Use MemberAutoData for variables or computed values.

How do I apply business defaults across many tests?

Create a custom AutoDataAttribute that builds an IFixture and applies fixture.Customize<T>() rules, then reuse that attribute on tests.