系列目錄html
在開始以前咱們先看一個陷阱web
用到的Person類以下數據庫
public class Person:IPerson { public string Name { get; set; } public int Age { get; set; } public DateTime BirthDay { get; set; } /// <summary> /// 判斷Name是否包含字母B /// </summary> /// <returns></returns> public bool WhetherNameContainsB() { if (this.Name == null) throw new ArgumentNullException("參數不能爲null"); if (this.Name.Contains("B")) return true; return false; } }
這個類之前也用過,有三個屬性和一個方法,其中方法用於判斷Name字段是否包含大寫字母B,若是包含返回true,不包含返回false,若是Name爲null則拋出異常ide
測試類以下函數
[TestFixture] public class FirstUnitTest { private Person psn; public FirstUnitTest() { psn = new Person(); } [Test] [Order(1)] public void SetPersonName() { psn.Name = "sto"; Assert.IsNotEmpty(psn.Name); } [Test] [Order(2)] public void DemoTest() { Assert.Throws<ArgumentNullException>(() => psn.WhetherNameContainsB()); } }
第一個測試給Name賦值,而後斷言用戶名不爲空,這顯然應該是經過的單元測試
第二個測試用於斷言調用WhetherNameContainsB時會拋異常,因爲這裏Name並無賦值,因此會拋出異常,這裏也應該能返回成功.測試
然而運行以上代碼第二個測試返回的是失敗!這是由於Nunit在運行測試類的時候會調用全部的測試方法,因爲咱們顯式指定的運行順序(使用order註解)
則第一個方法先於第二個方法前執行,因爲第一個方法把Name設置爲"sto",所以這時候全局psn的Name字段便有值了.因此第二個方法再調用psn的WhetherNameContainsB方法時,是不會拋出異常的(方法的邏輯是隻有Name有值便不會拋出異常).this
若是不指定運行順序,則第二個方法運行的結果是不肯定的,若是它先於第一個方法執行,則就會返回成功,若是晚於第一個方法則返回失敗.設計
咱們前面說到,單元測試的結果應該是穩定的,然而這裏倒是不肯定的,所以咱們要從新設計.日誌
固然其實解決這個問題很簡單,只要把對全局的變量移動到方法裏面就好了,這樣每一個方法的狀態就不會被外部改變了.
改造後的測試類以下
[TestFixture] public class FirstUnitTest { public FirstUnitTest() { } [Test] [Order(1)] public void SetPersonName() { Person psn = new Person(); psn.Name = "sto"; Assert.IsNotEmpty(psn.Name); } [Test] [Order(2)] public void DemoTest() { Person psn = new Person(); Assert.Throws<ArgumentNullException>(() => psn.WhetherNameContainsB()); } }
咱們再運行,便都能經過了.
然而這樣設計有一個問題,第一若是多個測試方法都要用到這個對象,則須要複製不少,第二若是多個方法之間共用的代碼很是多,那麼每一個方法裏都要複製不少代碼,咱們前面說過單元測試裏的代碼應力求簡潔明瞭,而且複製一樣的代碼不利於維護.下面咱們介紹Nunit裏的Setup
Setup註釋
在單元測試類中若是把一個方法加上setup註解,則這個方法會先於其它未標的方法執行,而且每一個方法執行以前都會執行它
,若是在setup註解的方法內初始化對象,則每一個方法運行以前都會運行這個被註解的方法,則每次變量都從新初始化,不會再有數據被共享形成的各類問題了.咱們用setup改造後的測試類以下
[TestFixture] public class FirstUnitTest { private Person psn; public FirstUnitTest() { } [SetUp] public void Setup() { psn = new Person(); } [Test] [Order(1)] public void SetPersonName() { psn.Name = "sto"; Assert.IsNotEmpty(psn.Name); } [Test] [Order(2)] public void DemoTest() { Assert.Throws<ArgumentNullException>(() => psn.WhetherNameContainsB()); } }
咱們在標識爲Setup的方法裏初始化Person,這樣測試就能經過了
被Setup註解的方法名可任意取,只要符合命名規範便可
Nunit並不限制一個測試類中有多個Setup方法,可是強烈不建議這麼作.
OneTimeSetup也是在全部的測試方法運行以前運行,不一樣的是它並不像SetUp同樣每一個測試方法運行以前都會運行,而是在全部測試方法運行以前之運行一次.它適用這樣場景:好比說咱們程序裏的數據訪問封閉類,這個類裏面通常都是訪問數據庫的各類方法和一些私有的變量像鏈接字符串之類的,數據訪問方法裏只會去讀取這些字段而不去修改它.最爲重要的是每一個測試方法運行以前都去實體化一個這樣的類會很耗費資源.像這種類型即可以放在OneTimeSetup方法裏,在類建立的時候運行一次.
這個方法功能很像構造函數,它能作的工做通常構造函數也能作.
Teardown和Setup用法同樣,只是它是在測試方法運行以後才運行,若是咱們的測試方法裏有須要釋放的對象能夠在這個方法裏釋放.
它是在全部的方法都運行完以後才運行一次,功能上至關於析構函數,用於在測試類全部方法都執行完之後釋放掉類中使用的資源.
前面部分咱們講了如何在所在單元測試運行以前以及在每個單元測試以前如何運行一個特定的方法.下面講解如何在程序集運行以前和運行以後運行某一指定方法.
可能會有人懷疑這樣作的意義,的確,大部分時候咱們可能不須要在程序集運行以前或者以後運行某一方法,可是特定的狀況下這樣作確實會給測試帶來很大幫助.好比如下場景
在Web項目中可能會大量使用ConfigurationManager.AppSetting[xxx]來獲取web項目配置,這樣作給測試帶來難題
因爲單元測試的運行環境不少時候並不是在程序的輸出目錄,所以web項目使用到AppSetting配置的方法在web環境運行正常,可是在單元測試環境獲得的值都是Null,這將會致使測試時大量業務覆蓋不到.
在測試的時候咱們很難經過傳參來改變這個值,由於在程序中每每都是獲取AppSetting裏的值,而不是設置,所以它每每不包含在方法的參數裏.也就無法經過傳參來修改它.
咱們若是在Setup裏給AppSetting賦值,好比ConfigurationManager.AppSettings["user"] = "sto";這樣在運行的時候咱們即可以獲取到這個值了,可是AppSetting是全局的,可能程序中不少方法都用到了它,咱們在每一個測試方法裏都寫個Setup方法給它複製顯然很是boring.
這時候咱們能夠在程序集運行以前運行一個方法,在這個方法裏給AppSetting賦值,這樣測試方法運行的時候使用到AppSetting的地方就能夠獲取到值了.
要作到這一點,咱們須要新建一個類,並把類上加上SetUpFixture註解.而後方法上加上OneTimeSetUp和OneTimeTeardown註解.這樣Nunit就會在程序集加載的時候掃描到這個類,而後對它處理.
咱們看一下示例代碼
[SetUpFixture] public class AssemblySetup { [OneTimeSetUp] public void RunBeforeEveryMethod() { ConfigurationManager.AppSettings["user"] = "sto"; ConfigurationManager.AppSettings["age"] = "32"; } }
咱們新建這個類之後RunBeforeEveryMethod
便會在程序集中全部代碼運行以前運行了
咱們看運行結果
咱們能夠看到,在測試類中隨便找一個方法裏面去獲取值,均可以獲取到了.
前面咱們講解了如何在方法運行先後,在測試類的全部方法運行先後以及如何在程序集,下面咱們講一下如何自定義一個方法在測試方法運行以前/以後運行.
自定義方法的優點在於若是每一個測試類的setup裏運行的代碼基本相同,只是稍微有一點差別,這樣就會致使代碼重複的問題.好比咱們要在方法運行以前和以後記錄一些日誌,這樣咱們就能夠自定義一個方法實如今測試方法運行先後運行這個自定義方法,減小代碼重複.
要實現自定義運行方法,咱們要繼承TestactionAttribute
示例代碼以下
public class MyTestAction:TestActionAttribute { public override void BeforeTest(ITest test) { Console.WriteLine("★★★★★★★★★★" + test.FullName); } }
咱們用Console.WriteLine模擬.
Itest對象由Nunit在運行時注入.
而後咱們要在運行這個自定義方法的類上加上MyTestAction
註解便可.
自定義運行方法很是強大,還能夠提供參數,這樣會在大幅度減小類似代碼的重複,提升可維護性,你們要之後的測試中慢慢體會.