引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
引言
在我們之前的文章中介紹過使用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, "[email protected]"));
}
比如我設置了所有的 email
都叫[email protected]
[Fact]
public void Test_StaffNameIsJohnDoe()
{
// Arrange
Staff staff = _fixture.Create<Staff>();
// Act
// Assert
Assert.Equal("[email protected]", 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
就像是一個自動數據生成器,讓我們的單元測試變得更簡單、更高效。通過使用它,我們可以輕鬆地創建測試數據,專註於寫好測試邏輯,而不用為數據準備的瑣事煩惱.