Skip to content
dotnet-testing-autofixture-bogus-integration logo

AutoFixture 與 Bogus 整合應用指南

dotnet-testing-autofixture-bogus-integration

AutoFixture 與 Bogus 整合完整指南。結合匿名測試資料與真實感語意化資料產生。涵蓋 SpecimenBuilder 整合、混合產生器、測試資料工廠與循環參考處理。 Keywords: autofixture bogus integration, autofixture bogus, bogus integration, Faker, EmailSpecimenBuilder, PhoneSpecimenBuilder, NameSpecimenBuilder, 真實感測試資料, 語意化資料, 混合產生器, HybridTestDataGenerator, OmitOnRe...

SKILL.md

Full skill instructions

AutoFixture 與 Bogus 整合應用指南

適用情境

當被要求執行以下任務時,請使用此技能:

  • 整合 AutoFixture 與 Bogus 兩套工具
  • 建立混合測試資料產生器
  • 設計 ISpecimenBuilder 整合 Bogus 資料產生
  • 建立自訂 AutoData 屬性使用 Bogus
  • 處理循環參考問題
  • 建立統一的測試資料工廠
  • 設計測試基底類別整合資料產生功能

核心概念

為什麼需要整合?

AutoFixture 的優勢:

  • 快速產生匿名測試資料
  • 自動處理複雜物件結構
  • 良好的循環參考處理機制

Bogus 的優勢:

  • 產生真實感的語意化資料
  • 豐富的資料類型支援(Email、Phone、Address 等)
  • 對驗證比較友善的資料格式

整合後的效果:

// 整合前的問題
var user = fixture.Create<User>();
// user.Email 可能是 "Email1a2b3c4d",不像真實 Email

// 整合後
var user = integratedFixture.Create<User>();
// user.Email 是 "[email protected]"
// user.FirstName 是 "John"
// 其他屬性由 AutoFixture 自動填充

套件安裝

<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageReference Include="Bogus" Version="35.6.3" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />

整合架構

整合方式總覽

整合方式適用場景複雜度
屬性層級 SpecimenBuilder特定屬性使用 Bogus低
類型層級 SpecimenBuilder整個類型使用 Bogus中
混合產生器 (HybridGenerator)統一 API 整合中
整合工廠 (IntegratedFactory)完整測試場景建構高
自訂 AutoData 屬性xUnit 整合低

核心整合技術

1. 屬性層級 SpecimenBuilder

透過 ISpecimenBuilder 介面,根據屬性名稱決定是否使用 Bogus:

public class EmailSpecimenBuilder : ISpecimenBuilder
{
    private readonly Faker _faker = new();

    public object Create(object request, ISpecimenContext context)
    {
        if (request is PropertyInfo property && 
            property.Name.Contains("Email", StringComparison.OrdinalIgnoreCase))
        {
            return _faker.Internet.Email();
        }
        
        return new NoSpecimen();
    }
}

常用 SpecimenBuilder:

Builder匹配條件Bogus 方法
EmailSpecimenBuilder包含 "Email"Internet.Email()
PhoneSpecimenBuilder包含 "Phone"Phone.PhoneNumber()
NameSpecimenBuilderFirstName/​LastName/​FullNamePerson.FirstName/​LastName/​FullName
AddressSpecimenBuilderStreet/​City/​Postal/​CountryAddress.*
WebsiteSpecimenBuilder包含 "Website"Internet.Url()
CompanyNameSpecimenBuilderCompany 類型的 NameCompany.CompanyName()

2. 類型層級 SpecimenBuilder

為整個類型建立完整的 Bogus 產生器:

public class BogusSpecimenBuilder : ISpecimenBuilder
{
    private readonly Dictionary<Type, object> _fakers;

    public BogusSpecimenBuilder()
    {
        _fakers = new Dictionary<Type, object>();
        RegisterFakers();
    }

    private void RegisterFakers()
    {
        _fakers[typeof(User)] = new Faker<User>()
            .RuleFor(u => u.Id, f => f.Random.Guid())
            .RuleFor(u => u.FirstName, f => f.Person.FirstName)
            .RuleFor(u => u.Email, (f, u) => f.Internet.Email(u.FirstName))
            .Ignore(u => u.Company); // 避免循環參考

        _fakers[typeof(Address)] = new Faker<Address>()
            .RuleFor(a => a.Street, f => f.Address.StreetAddress())
            .RuleFor(a => a.City, f => f.Address.City());
    }

    public object Create(object request, ISpecimenContext context)
    {
        if (request is Type type && _fakers.TryGetValue(type, out var faker))
        {
            return GenerateWithFaker(faker);
        }
        return new NoSpecimen();
    }
}

3. 擴充方法整合

public static class FixtureExtensions
{
    /// <summary>
    /// 為 AutoFixture 加入 Bogus 整合功能
    /// </​summary>
    public static IFixture WithBogus(this IFixture fixture)
    {
        // 先處理循環參考
        fixture.WithOmitOnRecursion();

        // 加入屬性層級整合
        fixture.Customizations.Add(new EmailSpecimenBuilder());
        fixture.Customizations.Add(new PhoneSpecimenBuilder());
        fixture.Customizations.Add(new NameSpecimenBuilder());
        fixture.Customizations.Add(new AddressSpecimenBuilder());

        // 加入類型層級整合
        fixture.Customizations.Add(new BogusSpecimenBuilder());

        return fixture;
    }

    /// <summary>
    /// 處理循環參考
    /// </​summary>
    public static IFixture WithOmitOnRecursion(this IFixture fixture)
    {
        fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
            .ToList()
            .ForEach(b => fixture.Behaviors.Remove(b));
        fixture.Behaviors.Add(new OmitOnRecursionBehavior());
        return fixture;
    }

    /// <summary>
    /// 設定隨機種子
    /// </​summary>
    public static IFixture WithSeed(this IFixture fixture, int seed)
    {
        Bogus.Randomizer.Seed = new Random(seed);
        return fixture;
    }
}

循環參考處理

為什麼循環參考很重要?

public class User
{
    public Company? Company { get; set; }  // User 參考 Company
}

public class Company
{
    public List<User> Employees { get; set; } = new();  // Company 參考 User
}

問題:User → Company → Employees(User) → Company → ... 無限循環

解決方案:OmitOnRecursionBehavior

var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
    .ToList()
    .ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());

效果:

  • ✅ 避免 StackOverflowException
  • ✅ 循環參考的屬性設為 null 或空集合
  • ⚠️ 某些深層屬性可能為 null(這是預期行為)

自訂 AutoData 屬性

BogusAutoDataAttribute

public class BogusAutoDataAttribute : AutoDataAttribute
{
    public BogusAutoDataAttribute() 
        : base(() => new Fixture().WithBogus())
    {
    }
}

使用方式

[Theory]
[BogusAutoData]
public void 使用整合資料測試(User user, Address address)
{
    user.Email.Should().Contain("@");
    user.FirstName.Should().NotBeNullOrEmpty();
    address.City.Should().NotBeNullOrEmpty();
}

混合產生器

ITestDataGenerator 介面

public interface ITestDataGenerator
{
    T Generate<T>();
    IEnumerable<T> Generate<T>(int count);
    T Generate<T>(Action<T> configure);
}

HybridTestDataGenerator 實作

public class HybridTestDataGenerator : ITestDataGenerator
{
    private readonly IFixture _fixture;

    public HybridTestDataGenerator(int? seed = null)
    {
        _fixture = new Fixture()
            .WithBogus()
            .WithOmitOnRecursion();

        if (seed.HasValue)
        {
            Bogus.Randomizer.Seed = new Random(seed.Value);
        }
    }

    public T Generate<T>() => _fixture.Create<T>();

    public IEnumerable<T> Generate<T>(int count) 
        => Enumerable.Range(0, count).Select(_ => Generate<T>());

    public T Generate<T>(Action<T> configure)
    {
        var item = Generate<T>();
        configure(item);
        return item;
    }
}

整合測試資料工廠

IntegratedTestDataFactory

public class IntegratedTestDataFactory
{
    private readonly IFixture _fixture;
    private readonly Dictionary<Type, object> _cache = new();

    public IntegratedTestDataFactory(int? seed = null)
    {
        _fixture = new Fixture()
            .WithBogus()
            .WithOmitOnRecursion()
            .WithRepeatCount(3);

        if (seed.HasValue)
        {
            _fixture.WithSeed(seed.Value);
        }
    }

    public T CreateFresh<T>() => _fixture.Create<T>();

    public List<T> CreateMany<T>(int count = 3) 
        => _fixture.CreateMany<T>(count).ToList();

    public T GetCached<T>() where T : class
    {
        var type = typeof(T);
        if (_cache.TryGetValue(type, out var cached))
            return (T)cached;

        var instance = CreateFresh<T>();
        _cache[type] = instance;
        return instance;
    }

    public void ClearCache() => _cache.Clear();

    /// <summary>
    /// 建立完整測試場景
    /// </​summary>
    public TestScenario CreateTestScenario()
    {
        var company = CreateFresh<Company>();
        var users = CreateMany<User>(5);
        var orders = CreateMany<Order>(10);

        // 建立關聯
        foreach (var user in users)
        {
            user.Company = company;
        }

        company.Employees = users;

        return new TestScenario
        {
            Company = company,
            Users = users,
            Orders = orders
        };
    }
}

測試基底類別

TestBase 實作

public abstract class TestBase
{
    protected readonly IFixture Fixture;
    protected readonly HybridTestDataGenerator Generator;
    protected readonly IntegratedTestDataFactory Factory;

    protected TestBase(int? seed = null)
    {
        Fixture = new Fixture()
            .WithBogus()
            .WithOmitOnRecursion()
            .WithRepeatCount(3);

        if (seed.HasValue)
        {
            Fixture.WithSeed(seed.Value);
        }

        Generator = new HybridTestDataGenerator(seed);
        Factory = new IntegratedTestDataFactory(seed);
    }

    protected T Create<T>() => Fixture.Create<T>();

    protected List<T> CreateMany<T>(int count = 3) 
        => Fixture.CreateMany<T>(count).ToList();

    protected T Create<T>(Action<T> configure)
    {
        var instance = Create<T>();
        configure(instance);
        return instance;
    }
}

Seed 管理與可重現性

重要限制

由於 AutoFixture 和 Bogus 有不同的隨機數管理機制:

  • ✅ Seed 確保測試行為穩定性
  • ✅ Seed 確保資料格式一致性
  • ❌ 無法保證所有屬性值完全相同

建議做法

// 使用 Seed 確保穩定性
var factory = new IntegratedTestDataFactory(seed: 12345);

// 如果需要完全可重現,使用單一工具
var faker = new Faker<User>();
faker.UseSeed(12345);

使用範例

基本整合使用

[Fact]
public void AutoFixture_整合_Bogus_應能產生真實感資料()
{
    // Arrange
    var fixture = new Fixture().WithBogus();

    // Act
    var user = fixture.Create<User>();

    // Assert
    user.Email.Should().Contain("@");
    user.FirstName.Should().NotBeNullOrEmpty();
    user.Phone.Should().MatchRegex(@"[\d\-\(\)\s]+");
}

使用工廠建立測試場景

[Fact]
public void 工廠_應能建立完整的測試場景()
{
    // Arrange
    var factory = new IntegratedTestDataFactory(seed: 42);

    // Act
    var scenario = factory.CreateTestScenario();

    // Assert
    scenario.Company.Should().NotBeNull();
    scenario.Users.Should().HaveCount(5);
    scenario.Orders.Should().HaveCount(10);
    
    scenario.Users.Should().AllSatisfy(user =>
    {
        user.Company.Should().Be(scenario.Company);
        user.Email.Should().Contain("@");
    });
}

最佳實踐

建議做法

  1. 永遠先處理循環參考

    fixture.WithOmitOnRecursion().WithBogus();
    
  2. 為常用實體建立專用 SpecimenBuilder

  3. 使用 Seed 確保測試穩定性

  4. 建立測試基底類別統一資料產生邏輯

  5. 適當使用快取提升效能

避免事項

  1. ❌ 過度設計,保持簡單實用
  2. ❌ 期望整合環境完全可重現
  3. ❌ 忽略循環參考處理
  4. ❌ 在每個測試中重新建立 Fixture

整合與 AutoFixture/​Bogus 的比較

面向純 AutoFixture純 Bogus整合方案
資料真實感低高高
設定複雜度低中中
物件關聯處理自動手動自動
循環參考處理內建無整合
可重現性高高中
適用場景單元測試整合測試/原型兩者皆可

參考資源

原始文章

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

官方文件


相關技能


程式碼範例

請參考同目錄下的範例檔案:

  • templates/​specimen-builders.cs - SpecimenBuilder 實作範例
  • templates/​hybrid-generator.cs - 混合產生器與擴充方法
  • templates/​integrated-factory.cs - 整合工廠與測試基底類別

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View