Nunit測試基礎之簡單斷言
在開始本篇之前需要補充一些內容,通過前面搭建Nunit測試環境我們知道要使一個方法成為單元測試方法首先要在此方法所在類加上TestFixture注解,並且在該方法上添加上Test注解.
然而還有一點需要注意:所有進行單元測試的方法必須標識為public訪問級別,否則無法識別為單元測試方法
此外,單元測試方法還有以下特征
-
單元測試方法不帶返回參數,也即都是Void類型
由於單元測試方法都是用來斷言特定狀態的,因此返回值是沒有意義的.此外也不要嘗試在一個單元測試方法中調用另一個,這樣做違反的單元測試的初衷(一旦出現錯誤不知道是哪個方法出出現的,還需要借助單元調用去發現是哪個方法出現的) -
單元測試方法不能帶有參數
這里說的不能是指不能像普通方法一樣帶有普通的參數(可以帶基於注解的特殊參數)
如果像普通方法一樣帶參數,雖然編譯能通過,但是運行時會拋出異常. -
單元測試方法不能重載
這里說的不能是不應該,實踐中是可以的,但是重載方法會帶來無盡的麻煩,讀者可以自己實踐一下.
進行單元測試無非就是對不同參數引起方法出現不同結果的斷言(一般情況下所有的單元測試方法都有斷言)下面我們來看Nunit中最基本最常用的斷言
基本斷言
Assert.True()
Assert.True用於斷言布爾參數是否為true
Assert.True的重載方法還支持可空布爾參數
Assert.True還支持自定義錯誤提示
上面代碼改為如下
如果返回錯誤的時候,我們自定義的錯誤信息就會顯示出來.
其它的斷言方法也大都有此重載
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類如下
這個類里面包含一個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方法才能得到預期結果,但在實際中我們往往把比較器放在類外邊,如何在比較引用對象的時候加載一個比較器在后面章節會有介紹,這里先略過.
Nunit測試基礎之復雜斷言
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
用於使一個測試通過,但是出現警示信息.