AutoFixture是一個.NET庫,旨在簡化單元測試中的數據設置過程。通過自動生成測試數據,它幫助開發者減少測試代碼的編寫量,使得單元測試更加簡潔、易讀和易維護。AutoFixture可以用於任何.NET測試框架,如xUnit、NUnit或MSTest。 預設情況下AutoFixture生成的字 ...
AutoFixture
是一個.NET庫,旨在簡化單元測試中的數據設置過程。通過自動生成測試數據,它幫助開發者減少測試代碼的編寫量,使得單元測試更加簡潔、易讀和易維護。AutoFixture可以用於任何.NET測試框架,如xUnit、NUnit或MSTest。
預設情況下AutoFixture生成的欄位值很多時候都滿足不了測試需求,比如:
public class User
{
public int Id { get; set; }
public string Name { get; set; } = null!;
[EmailAddress]
public string? Email { get; set; }
[StringLength(512)]
public string? Address { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
如果直接使用 Create<T>()
生成的User對象,他會預設給你填充Id為隨機整數,Name和Email為一串Guid,顯然這裡的郵箱地址生成就不能滿足要求,並不是一個有效的郵箱格式
那麼如何讓AutoFixture按需生成有效的測試數據呢?方法其實有好幾種:
方法1:直接定製
var fixture = new Fixture();
fixture.Customize<User>(c => c
.With(x => x.Email, "特定值")
.Without(x => x.Id));
這裡,With方法用於指定屬性的具體值,而Without方法用於排除某些屬性不被自動填充。
方法2:使用匿名函數
這在需要對生成的數據進行更複雜的操作時非常有用。
var fixture = new Fixture();
fixture.Customize<User>(c => c.FromFactory(() => new User
{
Email = "通過工廠方法生成",
}));
方法3:實現ICustomization介面
對於更複雜的定製需求,可以通過實現ICustomization介面來創建一個定製化類。這種方法的好處是可以重用定製邏輯,並且使得測試代碼更加整潔。
public class MyCustomClassCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<User>(c => c
.With(x => x.Email, "自定義值")
.Without(x => x.Id));
}
}
// 使用定製化
var fixture = new Fixture();
fixture.Customize(new MyCustomClassCustomization());
方法4:使用Build<T>
方法
Build<T>
方法提供了一種鏈式調用的方式來定製類型的生成規則,這在只需要對單個對象進行簡單定製時非常方便。
var myCustomObject = fixture.Build<User>()
.With(x => x.Email, $"{Guid.NewId()}@example.com")
.Without(x => x.Id)
.Create();
最佳實踐:
這裡以xunit
測試框架為例,
我們需要提前引用AutoFixture
,AutoFixture.Xunit2
庫,實現一個UserAutoDataAttribute
類,繼承自InlineAutoDataAttribute
重寫GetData
方法,大致代碼如下:
public class UserAutoDataAttribute : InlineAutoDataAttribute
{
public UserAutoDataAttribute(params object[] values) : base(values)
{
ArgumentNullException.ThrowIfNull(values[0]);
}
public override IEnumerable<object[]> GetData(MethodInfo testMethod)
{
var fixture = new Fixture();
//這裡使用上面的4種方式的一種,亦或者根據自身情況定製!
var user = fixture.Build<User>()
//.With(x => x.Id, 0)
.Without(x => x.Id) //ID需要排除因為EFCore需要插入時自動生成
.With(x => x.Email, $"{Uuid7.NewUuid7()}@example.com") //郵箱地址,需要照規則生成
.Create();
yield return new object[] { Values[0], user };
}
}
下麵是一個測試用例,需要填充db,和一個自動生成的User參數
public class UnitOfWorkTests(ITestOutputHelper output)
{
[Theory]
[UserAutoData(1)]
[UserAutoData(2)]
public async Task MyUnitOfWorkTest(int db, User user)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddDbContext<TestDbContext>(options =>
{
options.UseInMemoryDatabase($"test-{db}");
});
services.AddUnitOfWork<TestDbContext>();
var provider = services.BuildServiceProvider();
var uow = provider.GetRequiredService<IUnitOfWork<TestDbContext>>();
//add user
await uow.GetRepository<User>().InsertAsync(user);
await uow.SaveChangesAsync();
// select user
var user2 = await uow.GetRepository<User>().FindAsync(1);
Assert.NotNull(user2);
// delete user
uow.GetRepository<User>().Delete(1);
var row = await uow.SaveChangesAsync();
Assert.Equal(1, row);
// select user
user2 = await uow.GetRepository<User>().GetFirstOrDefaultAsync(x => x.Id == 1);
Assert.Null(user2);
}
}
如果你已經習慣編寫單元測試,但還沒有使用AutoFixture
,那麼推薦你嘗試一下,也許你也會喜歡上TA