dotnet-testing-autofixture-basics
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-autofixture-basics
Agent 安装分布
Skill 文档
AutoFixture åºç¤:èªåç¢çæ¸¬è©¦è³æ
æ¦è¿°
AutoFixture æ¯ä¸åçº .NET å¹³å°è¨è¨çæ¸¬è©¦è³æèªåç¢çå·¥å ·ï¼å®çæ ¸å¿ç念æ¯ãå¿å測試ã(Anonymous Testing)ãéåæ¦å¿µèªçºï¼å¤§é¨åçæ¸¬è©¦é½ä¸æè©²ä¾è³´æ¼ç¹å®çè³æå¼ï¼èæè©²å°æ³¨æ¼é©èç¨å¼éè¼¯çæ£ç¢ºæ§ã
çºä»éº¼éè¦ AutoFixtureï¼
å³çµ±æ¸¬è©¦è³ææºåççé»ï¼
- 樣æ¿ç¨å¼ç¢¼éå¤ï¼90% çç¨å¼ç¢¼é½å¨æºåè³æï¼çæ£ç測試éè¼¯è¢«åæ²
- 測試ç¦é»æ¨¡ç³ï¼å¾é£å¿«éçè§£é忏¬è©¦å¨é©èä»éº¼
- ç¶è·å°é£ï¼ç¶ç©ä»¶çµæ§æ¹è®æï¼ææç¸é測試é½éè¦ä¿®æ¹
- è³æä¾è³´æ§ï¼æ¸¬è©¦å¯è½æå¤ä¾è³´æ¼ç¹å®çè³æå¼
- éè¤ç¨å¼ç¢¼ï¼ç¸åçè³ææºåé輯å¨å¤å測試ä¸éè¤åºç¾
AutoFixture å¯ä»¥ç使¯ Test Data Builder Pattern çèªååé²åçï¼è½èªåç¢çè¤éçæ¸¬è©¦è³æï¼è®æåå°æ³¨æ¼æ¸¬è©¦é輯æ¬èº«ã
å®è£å¥ä»¶
<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
æééå½ä»¤åå®è£ï¼
dotnet add package AutoFixture
dotnet add package AutoFixture.Xunit2
åºæ¬ä½¿ç¨æ¹å¼
Fixture é¡å¥è Create()
Fixture æ¯ AutoFixture çæ ¸å¿é¡å¥ï¼æä¾èªåç¢çæ¸¬è©¦è³æçè½åï¼
using AutoFixture;
[Fact]
public void AutoFixture_åºæ¬ä½¿ç¨_æç¢çææè³æ()
{
// Arrange
var fixture = new Fixture();
// Act - ç¢çåºæ¬åå¥
var id = fixture.Create<int>(); // 鍿©æ£æ´æ¸
var name = fixture.Create<string>(); // é¡ä¼¼ GUID æ ¼å¼çå串
var price = fixture.Create<decimal>(); // 鍿©åé²ä½æ¸
var isActive = fixture.Create<bool>(); // 鍿©å¸æå¼
var date = fixture.Create<DateTime>(); // 鍿©æ¥ææé
var guid = fixture.Create<Guid>(); // æ°ç GUID
// Assert
id.Should().BePositive();
name.Should().NotBeNullOrEmpty();
guid.Should().NotBe(Guid.Empty);
}
CreateMany() ç¢çéå
[Fact]
public void CreateMany_ç¢çéå_ææå¤åå
ç´ ()
{
var fixture = new Fixture();
// é è¨ç¢ç 3 åå
ç´
var products = fixture.CreateMany<Product>().ToList();
// æå®æ¸é
var moreProducts = fixture.CreateMany<Product>(10).ToList();
products.Should().HaveCount(3);
moreProducts.Should().HaveCount(10);
}
è¤éç©ä»¶èªå建æ§
AutoFixture è½å¤ èªå建æ§è¤éçç©ä»¶çµæ§ï¼
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Address Address { get; set; } // å·¢çç©ä»¶
public List<Order> Orders { get; set; } // éå屬æ§
}
[Fact]
public void è¤éç©ä»¶_æå®æ´å»ºæ§ææå±¤ç´()
{
var fixture = new Fixture();
var customer = fixture.Create<Customer>();
// ææå±¬æ§èªåå¡«å
¥å¼
customer.Should().NotBeNull();
customer.Id.Should().BePositive();
customer.Name.Should().NotBeNullOrEmpty();
customer.Address.Should().NotBeNull();
customer.Address.Street.Should().NotBeNullOrEmpty();
customer.Orders.Should().NotBeEmpty();
}
Build() 模å¼ï¼ç²¾ç¢ºæ§å¶
ç¶éè¦å°ç¹å®å±¬æ§é²è¡æ§å¶æï¼ä½¿ç¨ Build<T>() 模å¼ï¼
[Fact]
public void Build模å¼_æå®ç¹å®å±¬æ§()
{
var fixture = new Fixture();
var customer = fixture.Build<Customer>()
.With(x => x.Name, "測試客æ¶") // æå®åºå®å¼
.With(x => x.Age, 25) // æå®åºå®å¼
.Without(x => x.InternalId) // æé¤å±¬æ§
.Create();
customer.Name.Should().Be("測試客æ¶");
customer.Age.Should().Be(25);
customer.InternalId.Should().Be(default);
}
OmitAutoProperties() æ§å¶èªåè¨å®
[Fact]
public void OmitAutoProperties_å
è¨å®å¿
è¦å±¬æ§()
{
var fixture = new Fixture();
var customer = fixture.Build<Customer>()
.OmitAutoProperties() // ä¸èªåè¨å®ä»»ä½å±¬æ§
.With(x => x.Id, 123) // åªè¨å®éå¿ç屬æ§
.With(x => x.Name, "測試客æ¶")
.Create();
customer.Id.Should().Be(123);
customer.Name.Should().Be("測試客æ¶");
customer.Email.Should().BeNullOrEmpty(); // ä¿æé è¨å¼
customer.Age.Should().Be(0); // ä¿æé è¨å¼
}
循ç°åèèç
ç¶ç©ä»¶å å«å¾ªç°åèæï¼AutoFixture æä¾å ©ç¨®èççç¥ï¼
é è¨è¡çºï¼ThrowingRecursionBehavior
// é è¨ææåºä¾å¤
[Fact]
public void 循ç°åè_é è¨è¡çº_æåºä¾å¤()
{
var fixture = new Fixture();
// Category æ Parent å±¬æ§æåèªå·±ï¼é æå¾ªç°åè
Action act = () => fixture.Create<Category>();
act.Should().Throw<ObjectCreationException>();
}
OmitOnRecursionBehaviorï¼å¿½ç¥å¾ªç°åè
[Fact]
public void 循ç°åè_使ç¨OmitOnRecursion_æå建ç«()
{
var fixture = new Fixture();
// ç§»é¤é è¨çæåºä¾å¤è¡çº
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
// å å
¥å¿½ç¥å¾ªç°åèè¡çº
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
var category = fixture.Create<Category>();
category.Should().NotBeNull();
category.Name.Should().NotBeNullOrEmpty();
}
å ±ç¨åºåºé¡å¥
建è°å»ºç«åºåºé¡å¥ä¾çµ±ä¸èç循ç°åèï¼
public abstract class AutoFixtureTestBase
{
protected Fixture CreateFixture()
{
var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
return fixture;
}
}
public class CustomerServiceTests : AutoFixtureTestBase
{
[Fact]
public void ProcessOrder_æ£å¸¸è¨å®_æèçæå()
{
var fixture = CreateFixture();
var customer = fixture.Create<Customer>();
// 測試é輯...
}
}
xUnit æ´å
ä½¿ç¨ Fixture å ±äº«å®¢è£½å
public class ProductServiceTests
{
private readonly Fixture _fixture;
public ProductServiceTests()
{
_fixture = new Fixture();
// å
±åç客製åè¨å®
_fixture.Customize<ProductCreateRequest>(c => c
.With(x => x.Price, () => _fixture.Create<decimal>() % 10000)
.With(x => x.Name, () => $"Product-{_fixture.Create<string>()[..8]}")
);
}
[Fact]
public void CreateProduct_使ç¨å
±äº«Fixture_ææå建ç«()
{
var productData = _fixture.Create<ProductCreateRequest>();
var service = new ProductService();
var result = service.CreateProduct(productData);
result.Should().NotBeNull();
productData.Price.Should().BeLessThan(10000);
}
}
çµå Theory 測試
[Theory]
[InlineData(CustomerType.Regular)]
[InlineData(CustomerType.Premium)]
[InlineData(CustomerType.VIP)]
public void CalculateDiscount_ä¸å客æ¶é¡å_æå¥ç¨æ£ç¢ºææ£(CustomerType customerType)
{
var fixture = new Fixture();
var customer = fixture.Build<Customer>()
.With(x => x.Type, customerType)
.Create();
var order = fixture.Create<Order>();
var calculator = new DiscountCalculator();
var discount = calculator.Calculate(customer, order);
switch (customerType)
{
case CustomerType.Regular:
discount.Should().Be(0);
break;
case CustomerType.Premium:
discount.Should().BeInRange(0.05m, 0.10m);
break;
case CustomerType.VIP:
discount.Should().BeInRange(0.15m, 0.25m);
break;
}
}
å¿å測試åå
æ ¸å¿æ¦å¿µ
測試æè©²é注ãè¡çºãè䏿¯ãè³æããå¨å¤§å¤æ¸æ æ³ä¸ï¼æå並ä¸å¨ä¹å ·é«çè³æå¼æ¯ä»éº¼ï¼
// â
好çåæ³ï¼å°æ³¨æ¼æ¸¬è©¦é輯
[Fact]
public void AddCustomer_任使æå®¢æ¶_ææåæ°å¢()
{
var fixture = new Fixture();
var customer = fixture.Create<Customer>();
var repository = new CustomerRepository();
var result = repository.Add(customer);
result.Should().BeTrue();
}
// â é¿å
ï¼ä¾è³´é¨æ©å¼çå
·é«å
§å®¹
[Fact]
public void BadTest_ä¾è³´é¨æ©å¼()
{
var fixture = new Fixture();
var customer = fixture.Create<Customer>();
// é¯èª¤ï¼åè¨é¨æ©ç¢çç年齡æå¤§æ¼ 18
customer.Age.Should().BeGreaterThan(18); // å¯è½å¤±æ
}
// â
æ£ç¢ºï¼æç¢ºè¨å®ééµå¼
[Fact]
public void GoodTest_æç¢ºè¨å®ééµå¼()
{
var fixture = new Fixture();
var customer = fixture.Build<Customer>()
.With(x => x.Age, 25) // æç¢ºè¨å®
.Create();
var validator = new CustomerValidator();
var isValid = validator.IsAdult(customer);
isValid.Should().BeTrue(); // ç©©å®ççµæ
}
é²åæ¯è¼ï¼Test Data Builder vs AutoFixture
å³çµ± Test Data Builder (Day 03)
// éè¦æåå»ºç« Builder é¡å¥ (40+ è¡)
public class OrderBuilder
{
private int _id = 1;
private Customer _customer = new Customer { Name = "Default" };
private List<OrderItem> _items = new();
public OrderBuilder WithCustomer(Customer customer)
{
_customer = customer;
return this;
}
public OrderBuilder WithItems(params OrderItem[] items)
{
_items = items.ToList();
return this;
}
public Order Build() => new Order
{
Id = _id,
Customer = _customer,
Items = _items
};
}
AutoFixture æ¹å¼ (Day 10)
// é¶è¨å®ææ¬ï¼å°æ³¨æ¼æ¸¬è©¦é輯 (5 è¡)
var fixture = new Fixture();
var order = fixture.Build<Order>()
.With(x => x.Status, OrderStatus.Completed)
.Create();
æ¯è¼æè¦
| å±¤é¢ | Test Data Builder | AutoFixture |
|---|---|---|
| ç¨å¼ç¢¼è¡æ¸ | 40+ è¡ Builder + 測試 | 5 è¡æ¸¬è©¦ |
| ç¶è·ææ¬ | ç©ä»¶æ¹è®éæ´æ° Builder | èªå驿è®å |
| éç¼æé | å 寫 Builder å寫測試 | ç´æ¥å¯«æ¸¬è©¦ |
| 大éè³æ | éè¦è¿´å | CreateMany(100) |
| å¯è®æ§ | æ¥åèªææç¢º | éçè§£ AutoFixture |
實åæç¨å ´æ¯
Entity 測試
[Theory]
[InlineData(0, CustomerLevel.Bronze)]
[InlineData(15000, CustomerLevel.Silver)]
[InlineData(60000, CustomerLevel.Gold)]
[InlineData(120000, CustomerLevel.Diamond)]
public void GetLevel_ä¸åæ¶è²»éé¡_æå峿£ç¢ºçç´(decimal totalSpent, CustomerLevel expected)
{
var fixture = new Fixture();
var customer = fixture.Build<Customer>()
.With(x => x.TotalSpent, totalSpent)
.Create();
var level = customer.GetLevel();
level.Should().Be(expected);
}
DTO é©è
[Fact]
public void ValidateRequest_ææè³æ_æééé©è()
{
var fixture = new Fixture();
var request = fixture.Build<CreateCustomerRequest>()
.With(x => x.Name, fixture.Create<string>()[..50])
.With(x => x.Email, fixture.Create<MailAddress>().Address)
.With(x => x.Age, Random.Shared.Next(18, 78))
.Create();
var context = new ValidationContext(request);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(request, context, results, true);
isValid.Should().BeTrue();
}
大éè³ææ¸¬è©¦
[Fact]
public void ProcessBatch_大éè³æ_ææ£ç¢ºèç()
{
var fixture = new Fixture();
var records = fixture.CreateMany<DataRecord>(1000).ToList();
var processor = new DataProcessor();
var stopwatch = Stopwatch.StartNew();
var result = processor.ProcessBatch(records);
stopwatch.Stop();
result.ProcessedCount.Should().Be(1000);
result.ErrorCount.Should().Be(0);
stopwatch.ElapsedMilliseconds.Should().BeLessThan(10000);
}
æä½³å¯¦è¸
æè©²å
- 使ç¨å¿å測試æ¦å¿µ – å°æ³¨æ¼æ¸¬è©¦é輯èéå ·é«è³æ
- åªå¨å¿
è¦æåºå®ç¹å®å¼ – 使ç¨
Build<T>().With()è¨å®ééµå±¬æ§ - 建ç«å ±ç¨åºåºé¡å¥ – çµ±ä¸èç循ç°åèçå ±åé ç½®
- åççéåå¤§å° – æ ¹ææ¸¬è©¦ç®ç調æ´
CreateMany()æ¸é
æè©²é¿å
- é度ä¾è³´é¨æ©å¼ – ä¸è¦åè¨é¨æ©å¼çå ·é«å §å®¹
- 忽ç¥éçå¼ – ä»éè¦æç¢ºæ¸¬è©¦éçæ æ³
- æ¿«ç¨èªåç¢ç – ç°¡å®æ¸¬è©¦å¯è½ç¨åºå®å¼æ´æ¸ æ¥
æ··åçç¥å»ºè°
çµåå ©ç¨®æ¹å¼çåªé»ï¼
public static class TestDataFactory
{
private static readonly Fixture _fixture = new();
// ç¨ AutoFixture 建ç«åºç¤è³æï¼åç¨ Builder å å·¥
public static OrderBuilder AnOrder()
{
var baseOrder = _fixture.Create<Order>();
return new OrderBuilder(baseOrder);
}
// 大é鍿©è³æç¢ç
public static IEnumerable<User> CreateRandomUsers(int count)
{
return _fixture.CreateMany<User>(count);
}
}
ç¨å¼ç¢¼ç¯æ¬
è«åè templates è³æå¤¾ä¸çç¯ä¾æªæ¡ï¼
- basic-autofixture-usage.cs – åºæ¬ AutoFixture ä½¿ç¨æ¹å¼
- complex-object-scenarios.cs – è¤éç©ä»¶è循ç°åèèç
- xunit-integration.cs – xUnit æ´åè Theory 測試
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 10 – AutoFixture åºç¤ï¼èªåç¢çæ¸¬è©¦è³æ
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10375018
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day10