第一單元 單元測試

来源:https://www.cnblogs.com/xuyubing/archive/2023/05/31/17447287.html
-Advertisement-
Play Games

1. 為什麼需要單元測試 在我們之前,測試某些功能是否能夠正常運行時,我們都將代碼寫到Main方法中,當我們測試第二個功能時,我們只能選擇將之前的代碼清掉,重新編寫。此時,如果你還想重新測試你之前的功能時,這時你就顯得有些難為情了,因為代碼都被你清掉了。當然你完全可以把代碼寫到一個記事本中進行記錄, ...


1. 為什麼需要單元測試

在我們之前,測試某些功能是否能夠正常運行時,我們都將代碼寫到Main方法中,當我們測試第二個功能時,我們只能選擇將之前的代碼清掉,重新編寫。此時,如果你還想重新測試你之前的功能時,這時你就顯得有些難為情了,因為代碼都被你清掉了。當然你完全可以把代碼寫到一個記事本中進行記錄,但是這樣總歸沒有那麼方便。當然你也可以重新新建一個項目來測試新的功能,但隨著功能越來越多,重新新建項目使得項目越來越多,變得不易維護,此時你若選擇使用單元測試功能,就可以完美解決你的困擾。

NUnit 提供了單元測試能力,也是目前用的比較流行的單元測試組件。

2. 什麼是NUnit?

官網: NUnit.org

NUnit 是適用於所有 .Net 語言的單元測試框架。最初從 JUnit 移植,當前的生產版本 3 已完全重寫,具有許多新功能和對各種 .NET 平臺的支持。

NUnit 項目是 .NET 基金會的成員。NUnit由核心團隊Rob ProuseCharlie PooleTerje SandstromJoseph MusserMikkel Nylander Bundgaard運營。.NET 基金會將提供指導和支持,以幫助確保項目的未來。

NUnit的成功是通過我們許多貢獻者和團隊成員的辛勤工作實現。核心團隊感謝每個人的幫助和貢獻,使NUnit取得了成功。據最新統計,各種NUnit軟體包在 NuGet.org 上通過了1.26億次下載。如果沒有許多志願者的奉獻精神,我們就無法做到這一點,他們為該項目貢獻了自己的時間和知識。

 

3. NUnit V3 使用

  1. 通過Nuget 安裝 如下三個包

    1. NUnit

    2. Microsoft.NET.Test.Sdk

    3. NUnit3TestAdapter

NUnit 包應由每個測試程式集引用,但不應由任何其他測試程式集引用。

NUnit使用自定義特性來標識測試。 所有的NUnit屬性都包含在NUnit中的 框架的名稱空間。 包含測試的每個源文件必須包含該名稱空間的using語句,項目必須引用框架程式集nunit.framework.dll。

4. 特性

AttributeUsage
Apartment Attribute 指示測試應在特定單元中運行。
Author Attribute 提供測試作者的姓名。
Category Attribute 為測試指定一個或多個類別。
Combinatorial Attribute 為提供的值的所有可能組合生成測試用例。
Culture Attribute 指定應為其運行測試或夾具的區域性。
Datapoint Attribute 提供數據理論.
DatapointSource Attribute 提供數據源理論.
DefaultFloatingPointTolerance Attribute 指示測試應使用指定的容差作為浮點數和雙重比較的預設值。
Description Attribute 將描述性文本應用於測試、測試修複或程式集。
Explicit Attribute 指示除非顯式運行,否則應跳過測試。
FixtureLifeCycle Attribute 指定夾具的生命周期,允許為每個測試用例構造測試夾具的新實例。在測試用例並行性很重要的情況下很有用。
Ignore Attribute 指示由於某種原因不應運行測試。
LevelOfParallelism Attribute 指定程式集級別的並行度級別。
MaxTime Attribute 指定測試用例成功的最長時間(以毫秒為單位)。
NonParallelizable Attribute 指定測試及其後代不能並行運行。
NonTestAssembly Attribute 指定程式集引用 NUnit 框架,但不包含測試。
OneTimeSetUp Attribute 標識在任何子測試之前要調用一次的方法。
OneTimeTearDown Attribute 標識在所有子測試之後要調用一次的方法。
Order Attribute 指定在包含的夾具或套件中運行裝飾測試的順序。
Pairwise Attribute 為提供的所有可能值對生成測試用例。
Parallelizable Attribute 指示測試和/或其後代是否可以並行運行。
Platform Attribute 指定應為其運行測試或夾具的平臺。
Property Attribute 允許在任何測試用例或夾具上設置命名屬性。
Random Attribute 指定生成隨機值作為參數化測試的參數。
Range Attribute 指定一系列值作為參數化測試的參數。
Repeat Attribute 指定應多次執行修飾方法。
RequiresThread Attribute 指示測試方法、類或程式集應在單獨的線程上運行。
Retry Attribute 如果測試失敗,則導致重新運行測試,最多可達最大次數。
Sequential Attribute 使用按提供的順序排列的值生成測試用例,無需其他組合。
SetCulture Attribute 設置測試持續時間內的當前區域性。
SetUICulture Attribute 設置測試持續時間內的當前 UI 區域性。
SetUp Attribute 指示在每個測試方法之前調用的 TestFixture 方法。
SetUpFixture Attribute 使用命名空間中所有測試夾具的一次性設置或拆卸方法標記類。
SingleThreaded Attribute 標記要求其所有測試在同一線程上運行的夾具。
TearDown Attribute 指示在每個測試方法之後調用的 TestFixture 方法。
Test Attribute 標記表示測試的 TestFixture 的方法。
TestCase Attribute 將帶有參數的方法標記為測試,並提供內聯參數。
TestCaseSource Attribute 將帶有參數的方法標記為測試,並提供參數源。
TestFixture Attribute 將類標記為測試夾具,並可能提供內聯構造函數參數。
TestFixtureSetup Attribute 已棄用,同義詞: OneTimeSetUp Attribute.
TestFixtureSource Attribute 將類標記為測試夾具,併為構造函數參數提供源。
TestFixtureTeardown Attribute 已棄用,同義詞: OneTimeTearDown Attribute.
TestOf Attribute 指示要測試的類的名稱或類型。
Theory Attribute 將測試方法標記為理論,這是NUnit中的一種特殊測試。
Timeout Attribute 為測試用例提供超時值(以毫秒為單位)。
Values Attribute 為測試方法的參數提供一組內聯值。
ValueSource Attribute 為測試方法的參數提供值的源

5. TestFixture

此屬性標記包含測試以及(可選)設置或拆解方法的類。

現在,對用作測試夾具的類的大多數限制都已消除。TestFixture類:

  • 可以是公共的、受保護的、私有的或內部的。

  • 可能是靜態類。

  • 可以是泛型的,只要提供了任何類型參數,或者可以從實際參數中推斷出來。

  • 可能不是抽象的 - 儘管該屬性可以應用於旨在用作TestFixture基類的抽象類。

  • 如果 TestFixtureAttribute 中沒有提供任何參數,則該類必須具有預設構造函數。

  • 如果提供了參數,則它們必須與其中一個構造函數匹配。

如果違反了這些限制中的任何一個,則該類不可作為測試運行,並且將顯示為錯誤。

建議構造函數沒有任何副作用,因為 NUnit 可能會在會話過程中多次構造對象。

從 NUnit 2.5 開始,TestFixture 屬性對於非參數化、非通用Fixture是可選的。只要該類包含至少一個標有 Test、TestCaseTestCaseSource 屬性的方法,它就會被視為測試夾具。

using NUnit.Framework;

namespace MyTest;

// [TestFixture] // 2.5 版本以後,可選
public class FirstTest
{

    [Test]
    public void Test1()
    {
        Console.WriteLine("test1,hello");
    }
    
}

6. SetUp 設置

此屬性在TestFixture內部使用,以提供在調用每個測試方法之前執行的一組通用函數。

SetUp 方法可以是靜態方法,也可以是實例方法,您可以在夾具中定義多個方法。通常,多個 SetUp 方法僅在繼承層次結構的不同級別定義,如下所述。

如果 SetUp 方法失敗或引發異常,則不會執行測試,並報告失敗或錯誤。

using NUnit.Framework;

namespace MyTest;

// [TestFixture] // 2.5 版本以後,可選
public class FirstTest
{
    [SetUp]
    public void Init()
    {
        Console.WriteLine("init,初始了一些數據");
    }

    private int a = 10;
    [OneTimeSetUp] // 只執行一次
    public void OneTime()
    {
        a++;
        Console.WriteLine("我只執行一次");
    }
    
    

    [Test]
    public void Test1()
    {
        Console.WriteLine("test1,hello");
        Console.WriteLine($"a的值是:{a}");
    }
    
}

輸出結果:

init,初始了一些數據
test1,hello

  

繼承

SetUp 屬性繼承自任何基類。因此,如果基類定義了 SetUp 方法,則會在派生類中的每個測試方法之前調用該方法。

您可以在基類中定義一個 SetUp 方法,在派生類中定義另一個方法。NUnit 將在派生類中調用基類 SetUp 方法之前調用基類 SetUp 方法。

 

警告

如果在派生類中重寫了基類 SetUp 方法,則 NUnit 將不會調用基類 SetUp 方法;NUnit 預計不會使用包括隱藏基方法在內的用法。請註意,每種方法可能都有不同的名稱;只要兩者都存在屬性,每個屬性都將以正確的順序調用。[SetUp]

 

筆記

  1. 儘管可以在同一類中定義多個 SetUp 方法,但您很少應該這樣做。與在繼承層次結構中的單獨類中定義的方法不同,不能保證它們的執行順序。

  2. 在 .NET 4.0 或更高版本下運行時,如有必要,可以指定非同步方法(c# 中的關鍵字)。

 

7. 斷言

斷言是任何 xUnit 框架中單元測試的核心,NUnit 也不例外。NUnit 提供一組豐富的斷言作為 Assert 類的靜態方法。

如果斷言失敗,則不返回方法調用並報告錯誤。如果測試包含多個斷言,則不會執行失敗的斷言後面的任何斷言。因此,通常最好嘗試每個測試一個斷言。

每個方法都可以在沒有消息的情況下調用,使用簡單的文本消息或消息和參數。在最後一種情況下,使用提供的文本和參數設置消息的格式。

如果確實存在等效項,則這兩種方法將始終給出相同的結果,因為經典方法的方法都是使用約束在內部實現的。例如。。。

Assert.AreEqual(4, 2+2);
Assert.That(2+2, Is.EqualTo(4));

 

Assert.True()

Assert.True用於斷言布爾參數是否為true Assert.True的重載方法還支持可空布爾參數

Assert.IsTrue

此斷言方法為Assert.True的親兄弟,二者功能一模一樣.

Assert.False

與Assert.True斷言狀態相反,斷言某一參數的結果為false 這裡需要特別說明的是,單元測試應該力求簡單,明瞭,斷言尤其如此.

Assert.Null

用於斷言一個變數是否為null,這裡不再舉例,但是實際中用的卻比較多.

Assert.NotNull

用於斷言一個變數不是null,它和Assert.Null()功能相同,只是斷言的狀態相反.

Nunit里還有其它的首碼有Not的方法,它和不帶Not的方法用法一樣,只是斷言的狀態相反

 

Assert.Throws 用於斷言特定方法在運行的時候會拋出異常.此方法有泛型版本,非同步版本,這裡僅對非同步版本進行說明 由於示例越來越複製,我們不能只在測試方法內寫一些簡單代碼進行測試了,這裡我們新建一個Person類如下

 

這個類裡面包含一個WhetherNameContainsB方法,用於判斷實例的Name是否包含字母B, 這個方法裡面有三個邏輯分支,單元測試的時候每一個都要覆蓋到,這裡我們斷言如果name為null則拋出ArgumentNullException 我們編寫如下單元測試方法

運行這個測試,則會返回成功狀態,因為預期的異常出現了.

Assert.IsEmpty

用於斷言欄位串是否為空字元串.

Assert.Positive

用於斷言數字類型(int,long,float,double,decimal等)為正數(大於零的數)

其實很多斷言都可以斷言都可以用Assert.True來完成,比如斷言一個數是否為正數,可以用Assert.True(a>0),這裡由於a只是一個普通變數,使用a>0作為條件主義仍然十分清析,然而到了後面有我們不僅要判斷一個變通變數,還要判斷lambda表達式,如果條件過於複雜,則語義會變得不是特別清析了,使用Assert自帶的靜態方法主義會更加清析,可讀性更高.

Assert.Negative

用於斷言數字類型為負數(小於零,不包括零)

Assert.Zero

用於斷言數字類型為數字零

Assert.NotZero

用於斷言數字類型不是零.

很多時候,Not包含的範圍非常廣,進行單元測試是為了在開發階段找出問題,解決問題,因此斷言的範圍越窄越好,我們不能僅僅讓單元測試通過了事. 比如一個方法返回的結果是數字類型,我們要斷定它是正數?大於某一個數的正數?在一定範圍的正數?是一個具體的正數?而不能簡單的是零,不是零.當然這還要根據業務本身來確實,有些時候範圍可能確實很大,但是一定要註意單元測試原則.

Assert.Greater(OrEqual)

用於斷言數字類型的變數大於(或者等於)某一個值

Assert.Less(OrEqual)

用於斷言數字類型小於(或者等於)某一值

Assert.Contains

用於斷言集合中是否包含某一元素. 比如以下方法,用於斷言字元串數組中是否包含特定字元串

Assert.AreSame

用於斷言兩個對象是否相等

這個靜態方法並沒有提供重載參數用於指定一個比較器來比較引用對象的相等性,需要實現equals和gethashcode方法才能得到預期結果,但在實際中我們往往把比較器放在類外邊,如何在比較引用對象的時候載入一個比較器在後面章節會有介紹,這裡先略過.

 

8. Nunit測試基礎之複雜斷言

上面一篇我們講解了一些基本斷言,利用這些斷言我們就可以進行單元測試了,然而僅僅使用簡單斷言還是不夠的,如果邏輯複雜度較高,使用簡單的斷言會導致單元測試代碼量增加,最終導致單元測試本身過於複雜和難以維護.需要說明的是這裡所說的複雜斷言仍然在Assert的靜態方法裡面,本身也不是特別複雜,只是比前面講的秒複雜一些,只是如果沒有了這些方法,一些特殊功能實現起來比較費勁基本無法實現.

下麵就介紹一下這些方法.

Assert.Catch

Assert.Catch有泛型和非同步方法,這裡只介紹其泛型方法.很多即使經常使用單元測試功能的人也未必用過這個方法. 其實這個方法和Assert.Throw用法上類似,只是有一點不同的是要測試的方法里的異常可以是catch到的異常的子類,實際開發中,如果我們能確立異常的類型,則最好捕獲具體類型異常,然而不能排除有一些不夠規範的代碼整段代碼被一個try catch包圍,這時候不一定能夠捕獲到想要的特定異常,這時候可以使用Assert.Catch

 

以上代碼類似上一節中講throw時使用的代碼,只是這裡泛型參數里是Exception而不是具體的異常信息,我們運行這段代碼,依然能夠測試通過. 在單元測試中,期待的狀態越具體越好,然而由於種種原因(比如立項時候沒有對代碼規範做過多要求,開發者水平不高,要測試的代碼是別人寫的,寫單元測試的人對其中邏輯並不是特別清楚等)我們無法做到非常具體,這個時候可以把要獲得的狀態放寬以後,待條件完備了再修改單元測試以進一步收窄狀態.

Assert.Ignore

Assert.Ignore和Ignore註解功能類似,可以在測試的時候忽略一個單元測試.有些情況下我們需要暫時忽略一個測試,比如說要進行測試的內容有一個外部依賴,現在外部依賴暫時不可用,如果我們不忽略的話測試將會失敗,在自動化環境下,失敗將導致無法進行下一步動作,此時我們可以暫時忽略這個測試. 忽略的測試前面有一個 黃色嘆號標誌,警示我們需要註意.

Assert. Fail

我們先看一下麵一段代碼

 

在這個單元測試本身使用到了try catch,我們知道WhetherNameContainsB方法在Person類的Name沒有提供值的情況下會拋出異常,然而我們的代碼並沒有斷言這個異常存在,此時由於catch代碼塊存在,會把異常吞掉,因此最終我們斷言person的Age為正數的時候將會通過(我們在構造類的時候設置了Age為32) 這顯然不行的,這時候我把們Assert.Fail(e.Message)取消註釋,測試便會變成失敗狀態.

Assert.IsNaN

用於斷言一個Double類型數字是否是NaN

 

雖然實際業務中我們並不會寫以上代碼,但是如果除數和被除數是通過複雜計算得來的則有可能除數和被除數都是零.

 

Assert.IsInstanceOf

用於斷言一個對象是否是指定類型的實例,

如上psn是Person類的一個實例,而Person繼承自Object,因此psn也是Object類的實例

 

Assert.IsAssignableFrom

此方法和以上方法作用相反,它用來斷言指定類型是當前對象類型的子類.(Assert.IsInstanceOf判斷的是當前對象是指定類型的子類) 這個方法語義不是很明確,很容易搞暈,使用的時候需要特別註意

Assert.Warn

用於使一個測試通過,但是出現警示信息.

 

 配套視頻鏈接:

C# 高級編程,.Net6 系列 開發第三階段,學完拿捏你的面試官,.net6 進階學習(已完結)_嗶哩嗶哩_bilibili

海闊平魚躍,天高任我行,給我一片藍天,讓我自由翱翔。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 基於 Web 實現 m3u8 視頻播放的簡單應用示例 ## 實現思路 將視頻(MP4 等)轉換為 M3U8 視頻的服務,可以按照以下步驟進行操作: 1. 將視頻(MP4 等)轉換為 M3U8:在服務中,使用適當的工具(如 FFmpeg)將接收到的視頻(MP4 等)轉換為 M3U8 格式。這將生成 ...
  • 裝飾器在 Python 中扮演了重要的角色,這是一種精巧的語言特性,讓我們能夠修改或增強函數和類的行為,無需修改它們的源代碼。這篇文章將深入探討裝飾器的所有相關主題,包括裝飾器的基礎知識、實現與使用、工作原理,以及通過實際例子學習裝飾器的獨特用法。 ...
  • ## 併發與並行😣 ### 併發與並行的概念和區別 並行:同一個時間段內多個任務同時在不同的CPU核心上執行。強調同一時刻多個任務之間的”**同時執行**“。 併發:同一個時間段內多個任務都在進展。強調多個任務間的”**交替執行**“。 ![](https://img2023.cnblogs.co ...
  • 摘要:在讀多寫少的環境中,有沒有一種比ReadWriteLock更快的鎖呢?有,那就是JDK1.8中新增的StampedLock! 本文分享自華為雲社區《【高併發】高併發場景下一種比讀寫鎖更快的鎖》,作者: 冰 河。 什麼是StampedLock? ReadWriteLock鎖允許多個線程同時讀取共 ...
  • ###BIO:同步阻塞 主線程發起io請求後,需要等待當前io操作完成,才能繼續執行。 ###NIO:同步非阻塞 引入selector、channel、等概念,當主線程發起io請求後,輪詢的查看系統是否準備好執行io操作,沒有準備好則主線程不會阻塞會繼續執行,準備好主線程會阻塞等待io操作完成。 # ...
  • 在前面的幾篇文章中,詳細地給大家介紹了Java里的集合。但在介紹集合時,我們涉及到了泛型的概念卻並沒有詳細學習,所以今天我們要花點時間給大家專門講解什麼是泛型、泛型的作用、用法、特點等內容 ...
  • # Rust Web 全棧開發之 Web Service 中的錯誤處理 ## Web Service 中的統一錯誤處理 ### Actix Web Service 自定義錯誤類型 -> 自定義錯誤轉為 HTTP Response - 資料庫 - 資料庫錯誤 - 串列化 - serde 錯誤 - I/ ...
  • 1. 透過現象看本質 反射被譽為是 c#中的黑科技 ,在很多領域中都有反射的身影,例如,我們經常使用的ORM框架,ABP框架 等。 反射指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。. 程式集包含模塊,而模塊包含類型,類型又包含成員。. 反射則提供了封裝程式集、模塊和類型的對象。. 您可以使 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...