.net持續整合測試篇之Nunit常見斷言

周國通發表於2019-08-02

系列目錄

Nunit測試基礎之簡單斷言

在開始本篇之前需要補充一些內容,通過前面搭建Nunit測試環境我們知道要使一個方法成為單元測試方法首先要在此方法所在類加上TestFixture註解,並且在該方法上新增上Test註解.
然而還有一點需要注意:所有進行單元測試的方法必須標識為public訪問級別,否則無法識別為單元測試方法

此外,單元測試方法還有以下特徵

  • 單元測試方法不帶返回引數,也即都是Void型別
    由於單元測試方法都是用來斷言特定狀態的,因此返回值是沒有意義的.此外也不要嘗試在一個單元測試方法中呼叫另一個,這樣做違反的單元測試的初衷(一旦出現錯誤不知道是哪個方法出出現的,還需要藉助單元呼叫去發現是哪個方法出現的)
  • 單元測試方法不能帶有引數
    這裡說的不能是指不能像普通方法一樣帶有普通的引數(可以帶基於註解的特殊引數)
    如果像普通方法一樣帶引數,雖然編譯能通過,但是執行時會丟擲異常.

  • 單元測試方法不能過載
    這裡說的不能是不應該,實踐中是可以的,但是過載方法會帶來無盡的麻煩,讀者可以自己實踐一下.
    進行單元測試無非就是對不同引數引起方法出現不同結果的斷言(一般情況下所有的單元測試方法都有斷言)下面我們來看Nunit中最基本最常用的斷言

基本斷言

Assert.True()

Assert.True用於斷言布林引數是否為true
Assert.True的過載方法還支援可空布林引數

Avatar

Assert.True還支援自定義錯誤提示
上面程式碼改為如下

Avatar

如果返回錯誤的時候,我們自定義的錯誤資訊就會顯示出來.

其它的斷言方法也大都有此過載

Assert.IsTrue

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

ssert.False

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

Assert.False(!firstCondition);
這和斷言變數firstCondition為true最終功能一樣,但是看上去很不直接明瞭,通常情況下我見到Assert.False第一反應就是斷言一個變數為False,這裡則反其道行之,實際上是斷言一個變數為true,這種情況應當避免.

Assert.IsFalse

Assert.False的親兄弟,二者表現一模一樣

Nunit Assert類還有還多其它的前面帶有Is的方法,它們都和不帶Is的一模一樣,其中帶Is的是為了相容老版本寫法.

Assert.Null

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

Assert.NotNull

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

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

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

Avatar

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

Avatar

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

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

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

Avatar

Assert.AreSame

用於斷言兩個物件是否相等

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

Nunit測試基礎之複雜斷言

Nunit測試基礎之複雜斷言

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

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

Assert.Catch

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

Avatar

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

Assert.Ignore

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

Assert. Fail

我們先看一下面一段程式碼

Avatar

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

Assert.IsNaN

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

Avatar

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

Assert.IsInstanceOf

用於斷言一個物件是否是指定型別的例項,

Avatar

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

Assert.IsAssignableFrom

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

Assert.Warn

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

相關文章