在我们之前的文章中介绍过使用
Bogus
生成模拟测试数据,今天来讲解一下功能更加强大自动生成测试数据的工具的库
"AutoFixture"
。
AutoFixture
AutoFixture
是一个针对
.NET
的开源库,旨在最大程度地减少单元测试中的
“安排(Arrange)”
阶段,以提高可维护性。它的主要目标是让开发人员专注于被测试的内容,而不是如何设置测试场景,通过更容易地创建包含测试数据的对象图,从而实现这一目标。
AutoFixture
可以帮助开发人员自动生成测试数据,减少手动设置测试数据的工作量,提高单元测试的效率和可维护性。通过自动生成对象,开发人员可以更专注于编写测试逻辑,而不必花费大量精力在准备测试数据上。
其实和
Bogus
相比,
AutoFixture
更强大的地方在于可以自动化设置对象的值,当类发生变化时如属性名或者类型更改,我们不需要去进行维护,
AutoFixture
可以自动适应
Class
的变化。
AutoFixture
与流行的
.NET
测试框架(如
NUnit
和
xUnit
)可以无缝集成。
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.Api
和
Sample.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
CreateMany<T>
AutoFixture
T
T
包括基本类型(如
string
、
int
)、自定义对象等
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);
}
修改后的例子中,我们使用
AutoFixture
的
Create<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);
}
AutoFixture
的
Build
方法结合
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 是否更新
}
创建三个对象,在第三个创建过程中把第一个的对象属性用第二个对象的属性覆盖。
使用自定义类型构建器来执行复杂的初始化,并且保证了创建多个相同的实例中要保持一致的自定义行为。
首先我们可以在我们的测试类构造函数中定义一个自定义规则
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 自定义类型的生成器
第一步安装
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
接口的模拟实例。
官网示例:
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
就像是一个自动数据生成器,让我们的单元测试变得更简单、更高效。通过使用它,我们可以轻松地创建测试数据,专注于写好测试逻辑,而不用为数据准备的琐事烦恼.
?欢迎关注笔者公众号一起学习交流,获取更多有用的知识~