• 自动化测试数据生成:Asp.Net Core单元测试利器AutoFixture详解


    引言

    在我们之前的文章中介绍过使用Bogus生成模拟测试数据,今天来讲解一下功能更加强大自动生成测试数据的工具的库"AutoFixture"

    什么是AutoFixture?

    AutoFixture 是一个针对 .NET 的开源库,旨在最大程度地减少单元测试中的“安排(Arrange)”阶段,以提高可维护性。它的主要目标是让开发人员专注于被测试的内容,而不是如何设置测试场景,通过更容易地创建包含测试数据的对象图,从而实现这一目标。

    AutoFixture 可以帮助开发人员自动生成测试数据,减少手动设置测试数据的工作量,提高单元测试的效率和可维护性。通过自动生成对象,开发人员可以更专注于编写测试逻辑,而不必花费大量精力在准备测试数据上。

    其实和Bogus相比,AutoFixture更强大的地方在于可以自动化设置对象的值,当类发生变化时如属性名或者类型更改,我们不需要去进行维护,AutoFixture可以自动适应Class的变化。

    AutoFixture与流行的 .NET 测试框架(如 NUnitxUnit)可以无缝集成。

    AutoFixture实战

    我们在创建xUnit单元测试项目dotNetParadise.AutoFixture

    安装依赖

    创建完项目之后我们首先要安装Nuget

    PM> NuGet\Install-Package AutoFixture -Version 4.18.1
    

    初始化

    AutoFixture的使用是从一个Fixture的实例对象开始的

    var fixture = new Fixture();
    

    接下来我们先创建一个测试类来学一下AutoFixture的使用

    public class AutoFixtureStaffTest
    {
        private readonly IFixture _fixture;
        public AutoFixtureStaffTest()
        {
            _fixture = new Fixture();
        }
    }
    

    实战

    我们之前的测试项目创建了Sample.ApiSample.Repository两个类库来做我们被测试的项目,本章继续使用Sample.Repository来演示AutoFixture的使用。

    dotNetParadise.AutoFixture 测试项目添加Sample.Repository的项目引用

    Sample.Repository中我们有一个Staff的实体对象,继续用作我们的测试

    public class Staff
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public int? Age { get; set; }
        public List? Addresses { get; set; }
        public DateTimeOffset? Created { get; set; }
        public void Update(Staff staff)
        {
            this.Name = staff.Name;
            this.Email = staff.Email;
            this.Age = staff.Age;
            this.Addresses = staff.Addresses;
            Created = staff.Created;
        }
    }
    

    属性赋值

       [Fact]
       public void Staff_SetProperties_ValuesAssignedCorrectly()
       {
           //Arrange
           Staff staff = new Staff();
           //生成Int类型
           staff.Id = _fixture.Create();
           //生成string 类型
           staff.Name = _fixture.Create();
           //生成DateTimeOffset类型
           staff.Created = _fixture.Create();
           //生成 List?
           staff.Addresses = _fixture.CreateMany(Random.Shared.Next(1, 100)).ToList();
           //Act
           //...省略
           // Assert
           Assert.NotNull(staff); // 验证 staff 对象不为 null
    
           // 验证 staff.Id 是 int 类型
           Assert.IsType(staff.Id);
    
           // 验证 staff.Name 是 string 类型
           Assert.IsType(staff.Name);
    
           // 验证 staff.Created 是 DateTimeOffset? 类型
           Assert.IsType(staff.Created);
    
           // 验证 staff.Addresses 是 List 类型
           Assert.IsType>(staff.Addresses);
    
           // 验证 staff.Addresses 不为 null
           Assert.NotNull(staff.Addresses);
    
           // 验证 staff.Addresses 中的元素数量在 1 到 100 之间
           Assert.InRange(staff.Addresses.Count, 1, 100);
       }
    

    示例中用到 AutoFixture 提供的的方法随机分配随机值,上面的示例中用到使用到了两个方法

    Create方法

    • 用于生成一个指定类型 T 的实例。它会自动填充对象的属性和字段,以便创建一个完整的对象实例。
    • 这个方法通常用于生成单个对象实例,适用于需要单个对象作为测试数据的情况。
    • 当调用 Create 方法时,AutoFixture 会根据 T 类型的构造函数、属性和字段来自动生成合适的值,以确保对象实例的完整性和一致性。

    CreateMany方法

    • 用于生成多个指定类型 T 的实例,通常用于生成集合或列表类型的测试数据。
    • 这个方法允许你指定要生成的实例数量,并返回一个包含这些实例的 IEnumerable 集合。
    • 当调用 CreateMany 方法时,AutoFixture 会根据 T 类型的构造函数、属性和字段来生成指定数量的对象实例,以便填充集合或列表。

    T包括基本类型(如 stringint)、自定义对象等

    Create构造对象

    上面的例子我们自己实例化的对象然后对对象挨个赋值,目的是让大家对AutoFixture的使用有一个初步的认识,上面也解释到了Create的泛型参数T可以是自定义的对象,那么我们来简化一下上面的示例

    [Fact]
    public void Staff_ObjectCreation_ValuesAssignedCorrectly()
    {
        // Arrange
        Staff staff = _fixture.Create(); // 使用 AutoFixture 直接创建 Staff 对象
    
        // Act
        //...省略
    
        // Assert
        Assert.NotNull(staff); // 验证 staff 对象不为 null
    
        // 验证 staff.Id 是 int 类型
        Assert.IsType(staff.Id);
    
        // 验证 staff.Name 是 string 类型
        Assert.IsType(staff.Name);
    
        // 验证 staff.Created 是 DateTimeOffset? 类型
        Assert.IsType(staff.Created);
    
        // 验证 staff.Addresses 是 List 类型
        Assert.IsType>(staff.Addresses);
    
        // 验证 staff.Addresses 不为 null
        Assert.NotNull(staff.Addresses);
    
        // 验证 staff.Addresses 中的元素数量在 1 到 100 之间
        Assert.InRange(staff.Addresses.Count, 1, 100);
    }
    
    

    修改后的例子中,我们使用 AutoFixtureCreate() 方法直接创建了一个 Staff 对象,而不是手动为每个属性赋值。这样可以更简洁地生成对象实例。

    数据驱动测试

    在正常的同一个测试方法中使用不同的输入数据进行测试时,通常都是基于 Theory 属性配合InlineData或者MemberData来完成的,有了AutoFixture之后数据也不用我们自己造了,来看一下实战入门

    第一步Nuget安装依赖

    PM> NuGet\Install-Package AutoFixture.Xunit2 -Version 4.18.1
    

    [AutoData]属性

    [Theory, AutoData]
    public void Staff_Constructor_InitializesPropertiesCorrectly(
        int id, string name, string email, int? age, List addresses, DateTimeOffset? created)
    {
        // Act
        var staff = new Staff { Id = id, Name = name, Email = email, Age = age, Addresses = addresses, Created = created };
    
        // Assert
        Assert.Equal(id, staff.Id);
        Assert.Equal(name, staff.Name);
        Assert.Equal(email, staff.Email);
        Assert.Equal(age, staff.Age);
        Assert.Equal(addresses, staff.Addresses);
        Assert.Equal(created, staff.Created);
    }
    

    通过 AutoData 方法,测试方法的参数化设置变得更加简单和高效,使得编写参数化测试方法变得更加容易。

    [InlineAutoData]属性

    如果我们有需要提供的特定化参数,可以用[InlineAutoData]属性,具体使用可以参考如下案例

        [Theory]
        [InlineAutoData(1)]
        [InlineAutoData(2)]
        [InlineAutoData(3)]
        [InlineAutoData]
        public void Staff_ConstructorByInlineData_InitializesPropertiesCorrectly(
         int id, string name, string email, int? age, List addresses, DateTimeOffset? created)
        {
            // Act
            var staff = new Staff { Id = id, Name = name, Email = email, Age = age, Addresses = addresses, Created = created };
    
            // Assert
            Assert.Equal(id, staff.Id);
            Assert.Equal(name, staff.Name);
            Assert.Equal(email, staff.Email);
            Assert.Equal(age, staff.Age);
            Assert.Equal(addresses, staff.Addresses);
            Assert.Equal(created, staff.Created);
        }
    

    自定义对象属性值

    AutoFixtureBuild 方法结合 With 方法可以用于自定义对象的属性值

        [Fact]
        public void Staff_SetCustomValue_ShouldCorrectly()
        {
            var staff = _fixture.Build()
                .With(_ => _.Name, "Ruipeng")
                .Create();
            Assert.Equal("Ruipeng", staff.Name);
        }
    

    禁用属性自动生成

    AutoFixture 中,可以使用 OmitAutoProperties 方法来关闭自动属性生成,从而避免自动生成属性值。这在需要手动设置所有属性值的情况下很有用。

        [Fact]
        public void Test_DisableAutoProperties()
        {
            // Arrange
            var fixture = new Fixture();
            var sut = fixture.Build()
                             .OmitAutoProperties()
                             .Create();
    
            // Assert
            Assert.Equal(0, sut.Id); // 验证 Id 属性为默认值 0
            Assert.Null(sut.Name); // 验证 Name 属性为 null
            Assert.Null(sut.Email); // 验证 Email 属性为 null
            Assert.Null(sut.Age); // 验证 Age 属性为 null
            Assert.Null(sut.Addresses); // 验证 Addresses 属性为 null
            Assert.Null(sut.Created); // 验证 Created 属性为 null
        }
    

    Do 方法执行自定义操作

    Do 方法是 AutoFixture 中用于执行操作的方法,通常结合 Build 方法一起使用,用于在构建对象时执行自定义操作。让我详细解释一下 Do 方法的用法和作用:

    主要特点:

    • 执行操作:Do 方法允许在对象构建过程中执行自定义操作,例如向集合添加元素、设置属性值等。
    • 链式调用:可以通过链式调用多个 Do 方法,依次执行多个操作。
    • 灵活定制:通过 Do 方法,可以在对象构建过程中灵活地定制对象的属性值或执行其他操作。
      使用方法:
    • 结合 Build 方法:通常与 Build 方法一起使用,用于为对象构建器执行操作。
    • 执行自定义操作:在 Do 方法中传入一个 lambda 表达式,可以在 lambda 表达式中执行需要的操作。
    • 链式调用:可以多次调用 Do 方法,实现多个操作的顺序执行。
       [Fact]
       public void Test_UpdateMethod()
       {
           // Arrange
           var fixture = new Fixture();
           var staff1 = fixture.Create();
           var staff2 = fixture.Create();
    
           // 使用 Do 方法执行自定义操作
           var staff3 = fixture.Build()
                                     .Do(x => staff1.Update(staff2))
                                     .Create();
    
           // Assert
           Assert.Equal(staff2.Name, staff1.Name); // 验证 Name 是否更新
           Assert.Equal(staff2.Email, staff1.Email); // 验证 Email 是否更新
           Assert.Equal(staff2.Age, staff1.Age); // 验证 Age 是否更新
           Assert.Equal(staff2.Addresses, staff1.Addresses); // 验证 Addresses 是否更新
           Assert.Equal(staff2.Created, staff1.Created); // 验证 Created 是否更新
       }
    

    创建三个对象,在第三个创建过程中把第一个的对象属性用第二个对象的属性覆盖。

    Customize Type 自定义类型

    使用自定义类型构建器来执行复杂的初始化,并且保证了创建多个相同的实例中要保持一致的自定义行为。

    首先我们可以在我们的测试类构造函数中定义一个自定义规则

        public AutoFixtureStaffTest()
        {
            _fixture = new Fixture();
            _fixture.Customize(composer => composer.With(x => x.Email, "zhangsan@163.com"));
        }
    

    比如我设置了所有的 email 都叫zhangsan@163.com

        [Fact]
        public void Test_StaffNameIsJohnDoe()
        {
            // Arrange
            Staff staff = _fixture.Create();
    
            // Act
    
            // Assert
            Assert.Equal("zhangsan@163.com", staff.Email);
        }
    
    

    这个位置大概得思想就是这样,保证用到的多实例都有相同的行为,可以参考:
    使用 AutoFixture 自定义类型的生成器

    Auto-Mocking with Moq

    第一步安装Nuget

    PM> NuGet\Install-Package AutoFixture.AutoMoq -Version 4.18.1
    
    
        [Fact]
        public async Task Repository_Add_ShouleBeSuccess()
        {
            _fixture.Customize(new AutoMoqCustomization());
            var repoMock = _fixture.Create();
            Assert.NotNull(repoMock);
        }
    
    

    创建 Fixture 实例并使用 AutoMoqCustomization 进行定制化,以便自动模拟 Moq 对象。
    使用 Create() 方法创建一个可分配给 IInterface 接口的模拟实例。

    Auto-configured Mocks

    官网示例:

    fixture.Customize(new AutoMoqCustomization { ConfigureMembers = true });
    fixture.Inject(1234);
    
    var document = fixture.Create();
    Console.WriteLine(document.Id); // 1234
    

    当将 ConfigureMembers = true 添加到 AutoMoqCustomization 中时,不仅会作为自动模拟容器,还会自动配置所有生成的模拟对象,使其成员返回 AutoFixture 生成的值。
    使用 Inject(1234) 将整数值 1234注入到 Fixture 中。
    使用 Create() 创建一个 IDocument 接口的实例,并输出其 Id 属性值。

    更多

    Moq 框架中存在一些限制,其中自动配置模式无法设置具有 ref参数的方法,并且也无法配置泛型方法。然而,您可以使用 ReturnsUsingFixture 扩展方法轻松地设置这些方法。

    官网示例:

    converter.Setup(x => x.Convert("10.0"))
             .ReturnsUsingFixture(fixture);
    

    在这个示例中,使用 ReturnsUsingFixture 扩展方法手动设置了一个名为 Convert 的泛型方法的行为。
    当调用 Convert 方法并传入字符串"10.0" 时,ReturnsUsingFixture方法将使用fixture生成的值作为返回值。 通过使用ReturnsUsingFixture扩展方法,您可以绕过Moq框架的限制,手动设置具有ref` 参数或泛型方法的行为,以满足特定的测试需求.

    最后

    AutoFixture就像是一个自动数据生成器,让我们的单元测试变得更简单、更高效。通过使用它,我们可以轻松地创建测试数据,专注于写好测试逻辑,而不用为数据准备的琐事烦恼.

    😄欢迎关注笔者公众号一起学习交流,获取更多有用的知识~
    image


    __EOF__

  • 本文作者: 董瑞鹏
  • 本文链接: https://www.cnblogs.com/ruipeng/p/18163423
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Java进阶篇--Executors类创建常见线程池
    Unity-UML类图讲解
    CSDN一站式云服务开放内测,诚邀C站新老用户来抢鲜
    Python 画 箱线图
    前端怎么对表单进行正则 前端常用的校验属性
    电商秒杀项目收获(二)
    【云原生 | Kubernetes 系列】--Gitops持续交付 Argo Rollouts Analysis
    武汉便宜的ov通配符https证书
    性能测试之性能监控和性能优化
    Wireshark数据抓包分析之动态主机配置协议
  • 原文地址:https://www.cnblogs.com/ruipeng/p/18163423