第一單元 單元測試

来源: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
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...