自動化測試數據生成:Asp.Net Core單元測試利器AutoFixture詳解

来源:https://www.cnblogs.com/ruipeng/p/18163423
-Advertisement-
Play Games

引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...


引言

在我們之前的文章中介紹過使用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, "[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就像是一個自動數據生成器,讓我們的單元測試變得更簡單、更高效。通過使用它,我們可以輕鬆地創建測試數據,專註於寫好測試邏輯,而不用為數據準備的瑣事煩惱.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一般來說Maven都是配合著idea一起使用,下載依賴速度慢就去網上找個鏡像配置一下,但總會遇到莫名其妙的問題,比如鏡像源不生效、Error reading file pom.xml等等。今天詳細講解一下maven配置文件settings.xml的配置方法。 小知識 maven的配置文件存在於兩個地 ...
  • 1. JUnit 最佳實踐指南 原文: https://howtodoinjava.com/best-practices/unit-testing-best-practices-junit-reference-guide/ 我假設您瞭解 JUnit 的基礎知識。 如果您沒有基礎知識,請首先閱讀(已針 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...