第一單元 單元測試

来源: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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...