引言

在我们之前的文章中介绍过使用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<string>? 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<int>();
//生成string 类型
staff.Name = _fixture.Create<string>();
//生成DateTimeOffset类型
staff.Created = _fixture.Create<DateTimeOffset>();
//生成 List<string>?
staff.Addresses = _fixture.CreateMany<string>(Random.Shared.Next(1, 100)).ToList();
//Act
//...省略
// Assert
Assert.NotNull(staff); // 验证 staff 对象不为 null // 验证 staff.Id 是 int 类型
Assert.IsType<int>(staff.Id); // 验证 staff.Name 是 string 类型
Assert.IsType<string>(staff.Name); // 验证 staff.Created 是 DateTimeOffset? 类型
Assert.IsType<DateTimeOffset>(staff.Created); // 验证 staff.Addresses 是 List<string> 类型
Assert.IsType<List<string>>(staff.Addresses); // 验证 staff.Addresses 不为 null
Assert.NotNull(staff.Addresses); // 验证 staff.Addresses 中的元素数量在 1 到 100 之间
Assert.InRange(staff.Addresses.Count, 1, 100);
}

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

Create<T>方法

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

CreateMany<T>方法

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

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

Create<T>构造对象

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

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

修改后的例子中,我们使用 AutoFixtureCreate<Staff>() 方法直接创建了一个 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<string> 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<string> 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<Staff>()
.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<Staff>()
.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<Staff>();
var staff2 = fixture.Create<Staff>(); // 使用 Do 方法执行自定义操作
var staff3 = fixture.Build<Staff>()
.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<Staff>(composer => composer.With(x => x.Email, "zhangsan@163.com"));
}

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

    [Fact]
public void Test_StaffNameIsJohnDoe()
{
// Arrange
Staff staff = _fixture.Create<Staff>(); // 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<IStaffRepository>();
Assert.NotNull(repoMock);
}

创建 Fixture 实例并使用 AutoMoqCustomization 进行定制化,以便自动模拟 Moq 对象。

使用 Create<IInterface>() 方法创建一个可分配给 IInterface 接口的模拟实例。

Auto-configured Mocks

官网示例:

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

当将 ConfigureMembers = true 添加到 AutoMoqCustomization 中时,不仅会作为自动模拟容器,还会自动配置所有生成的模拟对象,使其成员返回 AutoFixture 生成的值。

使用 Inject<int>(1234) 将整数值 1234注入到 Fixture 中。

使用 Create<IDocument>() 创建一个 IDocument 接口的实例,并输出其 Id 属性值。

更多

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

官网示例:

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

在这个示例中,使用 ReturnsUsingFixture 扩展方法手动设置了一个名为 Convert 的泛型方法的行为。

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

最后

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

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

自动化测试数据生成:Asp.Net Core单元测试利器AutoFixture详解的更多相关文章

  1. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  2. Asp.Net Core 单元测试正确姿势

    背景 ASP.NET Core 支持依赖关系注入 (DI) 软件设计模式,并且默认注入了很多服务,具体可以参考 官方文档, 相信只要使用过依赖注入框架的同学,都会对此有不同深入的理解,在此无需赘言. ...

  3. Asp.net中web.config配置文件详解(一)

    本文摘自Asp.net中web.config配置文件详解 web.config是一个XML文件,用来储存Asp.NET Web应用程序的配置信息,包括数据库连接字符.身份安全验证等,可以出现在Asp. ...

  4. asp.net iis URLRewrite 实现方法详解

    原文 asp.net iis URLRewrite 实现方法详解 实现非常简单首先你要在你的项目里引用两个dll:actionlessform.dll.urlrewriter.dll,真正实现重写的是 ...

  5. scribefire 多博客管理利器 安装详解

    scribefire 多博客管理利器 安装详解 一.ScribeFire介绍 ScribeFire 是 Firefox (火狐浏览器)上著名的博客写作工具,目前已跨平台支持多浏览器(Firefox,C ...

  6. 使用 Swagger 自动生成 ASP.NET Core Web API 的文档、在线帮助测试文档(ASP.NET Core Web API 自动生成文档)

    对于开发人员来说,构建一个消费应用程序时去了解各种各样的 API 是一个巨大的挑战.在你的 Web API 项目中使用 Swagger 的 .NET Core 封装 Swashbuckle 可以帮助你 ...

  7. 利用T4模板生成ASP.NET Core控制器的构造函数和参数

    前言 在ASP.NET Core中引入了DI,并且通过构造函数注入参数,控制器中会大量使用DI注入各种的配置参数,如果配置注入的参数比较多,而且各个控制器需要的配置参数都基本一样的话,那么不断重复的复 ...

  8. 关于单元测试的思考--Asp.Net Core单元测试最佳实践

    在我们码字过程中,单元测试是必不可少的.但在从业过程中,很多开发者却对单元测试望而却步.有些时候并不是不想写,而是常常会碰到下面这些问题,让开发者放下了码字的脚步: 这个类初始数据太麻烦,你看:new ...

  9. 使用 xUnit 编写 ASP.NET Core 单元测试

    还记得 .NET Framework 的 ASP.NET WebForm 吗?那个年代如果要在 Web 层做单元测试简直就是灾难啊..NET Core 吸取教训,在设计上考虑到了可测试性,就连 ASP ...

  10. GitHub自动化部署(CD) asp.net core 5.0 项目(免费空间)

    这里我简单介绍一下使用Github自动化部署自己项目到Heroku云服务器上,Heroku竟然是一个很非常老牌的云平台服务商,竟然还没听说过,网上一查2010被Salesforce收购,网上有很多关于 ...

随机推荐

  1. PC页面全屏显示以及退出全屏显示

    //点击,进入全屏 $('#btn4').click(()=>{ let el = document.documentElement; let rfs = el.requestFullScree ...

  2. 工作中常用且容易遗忘的 CSS 样式清单

    注:本文转载自公众号 Vue中文社区的一篇文章 1.文字超出部分显示省略号 单行文本的溢出显示省略号(一定要有宽度)  p{    width:200rpx;    overflow: hidden; ...

  3. Oracle 索引原理

    B-Tree索引 一个B树索引只有一个根节点,它实际就是位于树的最顶端的分支节点. 可以用下图一来描述B树索引的结构.其中,B表示分支节点,而L表示叶子节点. 对于分支节点块(包括根节点块)来说,其所 ...

  4. 为 AI 而生的编程语言「GitHub 热点速览」

    Mojo 是一种面向 AI 开发者的新型编程语言.它致力于将 Python 的简洁语法和 C 语言的高性能相结合,以填补研究和生产应用之间的差距.Mojo 自去年 5 月发布后,终于又有动作了.最近, ...

  5. 将Map中对应的key和value赋值到对象中

    BeanUtils位于import org.apache.commons.beanutils.BeanUtils包下 其使用方法: Map<String, Object> objectMa ...

  6. 鸿蒙HarmonyOS实战-ArkUI组件(CustomDialog)

    一.CustomDialog CustomDialog组件是一种自定义对话框,可以通过开发人员根据特定的要求定制内容和布局.它允许开发人员创建一个完全可定制的对话框,可以显示任何类型的内容,例如文本. ...

  7. 【Kotlin】扩展属性、扩展函数

    1 类的扩展 ​ Kotlin 提供了扩展类或接口的操作,而无需通过类继承或使用装饰器等设计模式,来为某个类添加一些额外的属性或函数,我们只需要通过一个被称为扩展的特殊声明来完成.通过这种机制,我们可 ...

  8. Qt Create开发,修改 .Pro 文件改变 exe 的名称

    // .pro // 修改 TARGET 就可以改变生成的exe的名称 TARGET = Test // 要是生成的exe名称中需要带有空格,需要用到$$quote TARGET = $$quote( ...

  9. Kotlin 使用协程编写高效的并发程序

    概念: 轻量级的线程 协程允许我们在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完 全是由编程语言来控制的,和操作系统无关.这种特性使得高并发程序的运行效率得到了极大的提升. 依赖库: d ...

  10. WPF/C#:让绘制的图形可以被选中并将信息显示在ListBox中

    实现的效果 如果你对此感兴趣,可以接着往下阅读. 实现过程 绘制矩形 比如说我想绘制一个3行4列的表格: private void Button_Click_DrawRect(object sende ...