翼度科技»论坛 编程开发 .net 查看内容

.NET单元测试使用AutoFixture按需填充的几种方式和最佳实践

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
AutoFixture是一个.NET库,旨在简化单元测试中的数据设置过程。通过自动生成测试数据,它帮助开发者减少测试代码的编写量,使得单元测试更加简洁、易读和易维护。AutoFixture可以用于任何.NET测试框架,如xUnit、NUnit或MSTest。
默认情况下AutoFixture生成的字段值很多时候都满足不了测试需求,比如:
  1. public class User
  2. {
  3.         public int Id { get; set; }
  4.         public string Name { get; set; } = null!;
  5.         [EmailAddress]
  6.         public string? Email { get; set; }
  7.         [StringLength(512)]
  8.         public string? Address { get; set; }
  9.         public DateTime CreatedAt { get; set; } = DateTime.Now;
  10. }
复制代码
如果直接使用 Create()生成的User对象,他会默认给你填充Id为随机整数,Name和Email为一串Guid,显然这里的邮箱地址生成就不能满足要求,并不是一个有效的邮箱格式
那么如何让AutoFixture按需生成有效的测试数据呢?方法其实有好几种:
方法1:直接定制
  1. var fixture = new Fixture();
  2. fixture.Customize<User>(c => c
  3.     .With(x => x.Email, "特定值")
  4.     .Without(x => x.Id));
复制代码
这里,With方法用于指定属性的具体值,而Without方法用于排除某些属性不被自动填充。
方法2:使用匿名函数

这在需要对生成的数据进行更复杂的操作时非常有用。
  1. var fixture = new Fixture();
  2. fixture.Customize<User>(c => c.FromFactory(() => new User
  3. {
  4.     Email = "通过工厂方法生成",
  5. }));
复制代码
方法3:实现ICustomization接口

对于更复杂的定制需求,可以通过实现ICustomization接口来创建一个定制化类。这种方法的好处是可以重用定制逻辑,并且使得测试代码更加整洁。
  1. public class MyCustomClassCustomization : ICustomization
  2. {
  3.     public void Customize(IFixture fixture)
  4.     {
  5.         fixture.Customize<User>(c => c
  6.             .With(x => x.Email, "自定义值")
  7.             .Without(x => x.Id));
  8.     }
  9. }
  10. // 使用定制化
  11. var fixture = new Fixture();
  12. fixture.Customize(new MyCustomClassCustomization());
复制代码
方法4:使用Build方法

Build方法提供了一种链式调用的方式来定制类型的生成规则,这在只需要对单个对象进行简单定制时非常方便。
  1. var myCustomObject = fixture.Build<User>()
  2.                             .With(x => x.Email, $"{Guid.NewId()}@example.com")
  3.                             .Without(x => x.Id)
  4.                             .Create();
复制代码
最佳实践:

这里以xunit测试框架为例,
我们需要提前引用AutoFixture,AutoFixture.Xunit2库,实现一个UserAutoDataAttribute类,继承自InlineAutoDataAttribute 重写GetData方法,大致代码如下:
  1. public  class UserAutoDataAttribute : InlineAutoDataAttribute
  2.     {
  3.         public UserAutoDataAttribute(params object[] values) : base(values)
  4.         {
  5.             ArgumentNullException.ThrowIfNull(values[0]);
  6.         }
  7.         public override IEnumerable<object[]> GetData(MethodInfo testMethod)
  8.         {
  9.             var fixture = new Fixture();
  10.             //这里使用上面的4种方式的一种,亦或者根据自身情况定制!
  11.             var user = fixture.Build<User>()
  12.                  //.With(x => x.Id, 0)
  13.                  .Without(x => x.Id) //ID需要排除因为EFCore需要插入时自动生成
  14.                  .With(x => x.Email, $"{Uuid7.NewUuid7()}@example.com") //邮箱地址,需要照规则生成
  15.                  .Create();
  16.             yield return new object[] { Values[0], user };
  17.         }
  18.     }
复制代码
下面是一个测试用例,需要填充db,和一个自动生成的User参数
  1. public class UnitOfWorkTests(ITestOutputHelper output)
  2. {
  3.         [Theory]
  4.         [UserAutoData(1)]
  5.         [UserAutoData(2)]
  6.         public async Task MyUnitOfWorkTest(int db, User user)
  7.         {
  8.                 var services = new ServiceCollection();
  9.                 services.AddLogging();
  10.                 services.AddDbContext<TestDbContext>(options =>
  11.                  {
  12.                     options.UseInMemoryDatabase($"test-{db}");
  13.                 });
  14.                 services.AddUnitOfWork<TestDbContext>();
  15.                 var provider = services.BuildServiceProvider();
  16.                 var uow = provider.GetRequiredService<IUnitOfWork<TestDbContext>>();
  17.                 //add user
  18.                 await uow.GetRepository<User>().InsertAsync(user);
  19.                 await uow.SaveChangesAsync();
  20.                 // select user
  21.                 var user2 = await uow.GetRepository<User>().FindAsync(1);
  22.                 Assert.NotNull(user2);
  23.                 // delete user
  24.                 uow.GetRepository<User>().Delete(1);
  25.                 var row = await uow.SaveChangesAsync();
  26.                 Assert.Equal(1, row);
  27.                 // select user
  28.                 user2 = await uow.GetRepository<User>().GetFirstOrDefaultAsync(x => x.Id == 1);
  29.                 Assert.Null(user2);
  30.         }
  31. }
复制代码
如果你已经习惯编写单元测试,但还没有使用AutoFixture,那么推荐你尝试一下,也许你也会喜欢上TA

来源:https://www.cnblogs.com/vipwan/p/18311419
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具