dotnet-testing-autofixture-bogus-integration
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-autofixture-bogus-integration
Agent 安装分布
Skill 文档
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 æ¯ "john.doe@example.com"
// 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() |
| NameSpecimenBuilder | FirstName/LastName/FullName | Person.FirstName/LastName/FullName |
| AddressSpecimenBuilder | Street/City/Postal/Country | Address.* |
| WebsiteSpecimenBuilder | å å« “Website” | Internet.Url() |
| CompanyNameSpecimenBuilder | Company é¡åç Name | Company.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("@");
});
}
æä½³å¯¦è¸
建è°åæ³
-
æ°¸é å èç循ç°åè
fixture.WithOmitOnRecursion().WithBogus(); -
çºå¸¸ç¨å¯¦é«å»ºç«å°ç¨ SpecimenBuilder
-
ä½¿ç¨ Seed ç¢ºä¿æ¸¬è©¦ç©©å®æ§
-
å»ºç«æ¸¬è©¦åºåºé¡å¥çµ±ä¸è³æç¢çé輯
-
é©ç¶ä½¿ç¨å¿«åæåæè½
é¿å äºé
- â é度è¨è¨ï¼ä¿æç°¡å®å¯¦ç¨
- â æææ´åç°å¢å®å ¨å¯éç¾
- â 忽ç¥å¾ªç°åèèç
- â 卿¯å測試ä¸éæ°å»ºç« Fixture
æ´åè AutoFixture/Bogus çæ¯è¼
| é¢å | ç´ AutoFixture | ç´ Bogus | æ´åæ¹æ¡ |
|---|---|---|---|
| è³æç實æ | ä½ | é« | é« |
| è¨å®è¤é度 | ä½ | ä¸ | ä¸ |
| ç©ä»¶éè¯èç | èªå | æå | èªå |
| 循ç°åèèç | å §å»º | ç¡ | æ´å |
| å¯éç¾æ§ | é« | é« | ä¸ |
| é©ç¨å ´æ¯ | å®å 測試 | æ´å測試/åå | å ©è çå¯ |
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 15 – AutoFixture è Bogus æ´åï¼çµåå
©è
åªå¢
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10375620
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day15
宿¹æä»¶
ç¸éæè½
- autofixture-basics – AutoFixture åºç¤ä½¿ç¨
- autofixture-customization – AutoFixture èªè¨åçç¥
- autodata-xunit-integration – AutoData å±¬æ§æ´å
- bogus-fake-data – Bogus åè³æç¢çå¨
ç¨å¼ç¢¼ç¯ä¾
è«åèåç®éä¸çç¯ä¾æªæ¡ï¼
templates/specimen-builders.cs– SpecimenBuilder 實ä½ç¯ä¾templates/hybrid-generator.cs– æ··åç¢çå¨èæ´å æ¹æ³templates/integrated-factory.cs– æ´åå·¥å» èæ¸¬è©¦åºåºé¡å¥