引言
在我們之前的文章中介紹過使用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
的例項,通常用於生成集合或列表型別的測試資料。 - 這個方法允許你指定要生成的例項數量,並返回一個包含這些例項的 IEnumerable 集合。
- 當呼叫
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 是否更新
}
建立三個物件,在第三個建立過程中把第一個的物件屬性用第二個物件的屬性覆蓋。
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
就像是一個自動資料生成器,讓我們的單元測試變得更簡單、更高效。透過使用它,我們可以輕鬆地建立測試資料,專注於寫好測試邏輯,而不用為資料準備的瑣事煩惱.
- AutoFixture
- 本文完整原始碼
😄歡迎關注筆者公眾號一起學習交流,獲取更多有用的知識~