NUnit2.0詳細使用方法

Just4life發表於2013-08-01
前一段時間,有人問我在.NET裡如何進行TDD開發.這個問題促使我想對NUnit做一個詳細的介紹.因為我們大家都知道NUnit是在.NET進行TDD的利器.
如果你已經知道很多關於NUnit的應用,請指出我的不對之處和提出一些建議,使本文更加完善.如果你對NUnit還不是很瞭解的話,我建議你還是閱讀一下.
本文分為以下部分:

1. TDD的簡介

首先什麼是TDD呢?Kent Beck在他的<<測試驅動開發 >>(Addison-Wesley Professional,2003)一書中,使用下面2個原則來定義TDD:
·除非你有一個失敗的自動測試,永遠不要寫一單行程式碼.
·阻止重複
我想第一個原則是顯而易見的.在沒有失敗的自動測試下就不要寫程式碼.因為測試是嵌入在程式碼必須滿足的需求中.如果沒有需求,就沒有必要實現任何東西.所以這個原則阻止我們去實現那些沒有測試和在解決方案中不需要的功能.
第二個原則說明了在一個程式中,不應該包含重複的程式碼.如果程式碼重複,我想這就是不好的軟體設計的象徵.隨著時間的流逝,它會對程式造成不一致的問題,並且使程式碼變非常混亂 ,因為我們時常不會記得重複程式碼的位置.如果發現程式碼重複,我想我們應該立即刪除程式碼重複.其實這就涉及到重構了.在這裡我就不多講了.
一般來說,測試分為2種型別,一是程式設計師自己的測試,另外一種是客戶的測試.關於客戶測試,我推薦一個FIT的框架,非常不錯。在這裡,我們講的TDD就是程式設計師測試.那麼什麼是程式設計師測試呢?我認為就是我們常說的單元測試.既然是單元測試,在.NET裡勢必會用到某些工具,目前最著名恐怕就是我即將介紹的NUnit了,

2.NUnit的介紹

NUnit是一個單元測試框架,專門針對於.NET來寫的.其實在前面有JUnit(Java),CPPUnit(C++),他們都是xUnit的一員.最初,它是從JUnit而來.現在的版本是2.2.接下來我所用的都是基於這個版本.
NUnit最初是由James W. Newkirk, Alexei A. Vorontsov 和Philip A. Craig, 後來開發團隊逐漸龐大起來.在開發過程中, Kent Beck 和Erich Gamma2位牛人也提供了許多幫助.看來對於NUnit還真是下了一番力氣了.J
NUnit是xUnit家族種的第4個主打產品,完全由C#語言來編寫,並且編寫時充分利用了許多.NET的特性,比如反射,客戶屬性等等.
最重要的一點是它適合於所有.NET語言.
如果你還沒有下載,可以到http://www.nunit.org/去下載.

2.1 NUnit的介紹

   Ok,下面正式講解NUnit.在講解之前,看看幾張圖片:
    
圖1  NUnit執行的效果

                     圖2   NUnit執行的另外一個效果
從中我們可以非常容易發現,右邊是個狀態條,圖1是紅色的,圖2是綠色的.為什麼會這樣呢?因為如果所有測試案例執行成功,就為綠色,反之如果有一個不成功,則為紅色,但也有黃色的.左面的工作域內則是我們寫的每一個單元測試.
通過上面的圖片,我想你對NUnit有個總的瞭解了.
接下來還是分為2個部分,一是NUnit的佈局,另外一部分就是它的核心概念.
首先熟悉一下NUnit GUI的佈局.
讓我們更進一步看一下測試執行器視窗的佈局。在右邊皮膚的中間,可以看到測試進度條。進度條的顏色反映了測試執行的狀態:
  • 綠色 描述目前所執行的測試都通過
  • 黃色 意味某些測試忽略,但是這裡沒有失敗
  • 紅色 表示有失敗
底部的狀態條表示下面的狀態:
  • 狀態.說明了現在執行測試的狀態。當所有測試完成時,狀態變為Completed.執行測試中,狀態是Running: <test-name> (<test-name>是正在執行的測試名稱)。
  • Test Cases說明載入的程式集中測試案例的總個數。這也是測試樹裡葉子節點的個數。
  • Tests Run 已經完成的測試個數。
  • Failures  到目前為止,所有測試中失敗的個數.
  • Time  顯示執行測試時間(以秒計)
File主選單有以下內容:
  • New Project允許你建立一個新工程。工程是一個測試程式集的集合。這種機制讓你組織多個測試程式集,並把他們作為一個組對待。
  • Open 載入一個新的測試程式集,或一個以前儲存的NUnit工程檔案。
  • Close關閉現在載入的測試程式集或現在載入的NUnit工程。
  • Save 儲存現在的Nunit工程到一個檔案。如果正工作單個程式集,本選單項允許你建立一個新的NUnit工程,並把它儲存在檔案裡。
  • Save As允許你將現有NUnit工程作為一個檔案儲存。
  • Reload 強制過載現有測試程式集或NUnit工程。NUnit-Gui自動監測現載入的測試程式集的變化。
當程式集變化時,測試執行器重新載入測試程式集。(當測試正執行時,現在載入的測試程式集不會重新載入。在測試執行之間測試程式集僅可以重新載入。一個忠告:如果測試程式集依賴另外一個程式集,測試執行器不會觀察任何依賴的程式集。對測試執行器來說,強制一個過載使全部依賴的程式集變化可見。
  • Recent Files  說明5個最近在NUnit中載入的測試程式集或NUnit工程(這個列表在Windows登錄檔,由每個使用者維護,因此如果你共享你的PC,你僅看到你的測試)。最近程式集的數量可以使用Options選單項修改,可以訪問Tool主選單。
  • Exit退出。
  • View選單有以下內容:
  • Expand一層層擴充套件現在樹中所選節點
  • Collapse 摺疊現在樹中選擇的節點
  • Expand All遞迴擴充套件樹中所選節點後的所有節點
  • Collapse All遞迴摺疊樹中所選節點後的所有節點
  • Expand Fixtures擴充套件樹中所有代表測試fixture的節點。
  • Collapse Fixtures 摺疊樹中所有代表測試fixture的節點。
  • Properties 顯示樹中現所選節點的屬性。
  • Tools 選單由這些項:
  • Save Results as XML作為一XML檔案儲存執行測試的結果。
  • Options讓你定製NUnit的行為。
現在看看右邊,你已經熟悉Run按鈕和進度條。這裡還有一個緊跟Run按鈕的Stop按鈕:點選這個按鈕會終止執行正執行的測試。進度條下面是一個文字視窗,在它上方,由以下4個標籤:
  • Errors and Failures 視窗顯示失敗的測試。在我們的例子裡,這個視窗是空。
  • Tests Not Run 視窗顯示沒有得到執行的測試。
  • Console.Error 視窗顯示執行測試產生的錯誤訊息。這些此訊息是應用程式程式碼使用Console.Error輸出流可以輸出的。
  • Console.Out視窗顯示執行測試列印到Console.Error輸出流的文字訊息。

2.2 一些常用屬性

接下來,我將講述這個框架如何使用.同時也涉及到一些非常重要的概念,我想其客戶屬性是非常重要的.在NUnit裡,有以下幾種屬性:
  • Test Fixture   
  • Test
下面我將對每種屬性一一講解.

TestFixtureAttribute

    本屬性標記一個類包含測試,當然setup和teardown方法可有可無.(關於setup 和teardown方法在後面介紹)
    做為一個測試的類,這個類還有一些限制
  • 必須是Public,否則NUnit看不到它的存在.   
  • 它必須有一個預設的建構函式,否則是NUnit不會構造它.   
  • 建構函式應該沒有任何副作用,因為NUnit在執行時經常會構造這個類多次,如果要是建構函式要什麼副作用的話,那不是亂了.
舉個例子
1 using System; 2using NUnit.Framework; 3namespace MyTest.Tests 4{ 5 6 [TestFixture] 7public class PriceFixture 8{ 9// 10 } 11} 12

TestAttribute

Test屬性用來標記一個類(已經標記為TestFixture)的某個方法是可以測試的.為了和先前的版本向後相容,頭4個字元(“test”)忽略大小寫.(參看http://nunit.org/test.html)
這個測試方法可以定義為:       
public void MethodName()
從上面可以看出,這個方法沒有任何引數,其實測試方法必須沒有引數.如果我們定義方法不對的話,這個方法不會出現在測試方法列表中.也就是說在NUnit的介面左邊的工作域內,看不到這個方法.還有一點就是這個方法不返回任何引數,並且必須為Public.
例如:
1using System; 2using NUnit.Framework; 3 4namespace MyTest.Tests 5{ 6 [TestFixture] 7public class SuccessTests 8{ 9 [Test] public void Test1() 10{ /**//* */ } 11 } 12} 1314
一般來說,有了上面兩個屬性,你可以做基本的事情了.



另外,我們再對如何進行比較做一個描述。

在NUnit中,用Assert(斷言)進行比較,Assert是一個類,它包括以下方法:AreEqual,AreSame,Equals, Fail,Ignore,IsFalse,IsNotNull,具體請參看NUnit的文件。

3.如何在.NET中應用NUnit

我將舉個例子,一步一步演示如何去使用NUnit.

第1步.為測試程式碼建立一個Visual Studio工程。

在Microsoft Visual Studio .NET中,讓我們開始建立一個新的工程。選擇Visual C#工程作為工程型別,Class Library作為模板。將工程命名為NUnitQuickStart.圖4-1是一個描述本步驟的Visual Studio .NET。

                            圖 4-1: 建立第一個NUnit工程

第2步.增加一個NUnit框架引用

在Microsoft Visual Studio .NET裡建立這個例子時,你需要增加一個NUnit.framework.dll引用,如下:
在Solution Explorer右擊引用,然後選擇增加引用
   NUnit.framework元件,在Add Reference對話方塊中按Select和OK按鈕。
圖4-2 描述了這步:

圖 4-2: 增加一個 NUnit.framework.dll 引用到工程

第3步.為工程加一個類.

為工程加一個NumbersFixture類。這裡是這個例子的程式碼。
1using System;
2using NUnit.Framework;
3 
4namespace NUnitQuickStart
5{
6            [TestFixture]
7            public class NumersFixture
8            {
9                        [Test]
10                        public void AddTwoNumbers()
11                        {
12                                    int a=1;
13                                    int b=2;
14                                    int sum=a+b;
15                                    Assert.AreEqual(sum,3);
16                        }

17            }

18}

19

第4步.建立你的Visual Studio 工程,使用NUnit-Gui測試

從程式->NUnit2.2開啟NUnit-gui,載入本本工程編譯的程式集.
為了在Visual Studio .NET中自動執行NUnit-Gui,你需要建立NUnit-Gui作為你的啟動程式:
在 Solution Explorer裡右擊你的NunitQuickStart工程。
在彈出選單中選擇屬性。
在顯示的對話方塊的左面,點選Configuration Properties夾
選擇出現在Configuration Properties夾下的Debugging。
在屬性框右邊的Start Action部分,選擇下拉框的Program作為Debug Mode值。
按Apply按鈕
設定NUnit-gui.exe 作為Start Application。,你既可以鍵入nunit-gui.exe的全路徑,也可使用瀏覽按鈕來指向它。
圖4-3 幫助描述本步驟:
 
圖 4-3:將NUnit-Gui 作為工程的測試執行器

第5步.編譯執行測試.

現在編譯solution。成功編譯後,開始應用程式。NUnit-Gui測試執行器出現。當你第一次開始NUnit-Gui,它開啟時沒有測試載入。從File選單選擇Oprn,瀏覽NUnitQuickStart.dll的路徑。當你載入了測試的程式集,測試執行器為載入的程式集的測試產生一個可見的表現。在例子中,測試程式集僅有一個測試,測試程式集的結構如圖4-4所示:

圖 4-4: 測試程式集的測試在 NUnit-Gui中的檢視
按Run按鈕。樹的節點變為綠色,而且測試執行器視窗上的進度條變綠,綠色代表成功通過。
 

4.其他的一些核心概念

上面的例子介紹了基本的NUnit特性和功能. TestFixture, Test, 和 Assert是3個最基本的特徵,我們可以用這些特性進行程式設計師測試了.但是有的時候,你覺得這3個遠遠不夠,比如有的時候開啟一個資料庫連線多次,有沒有隻讓它開啟一次的方法呢?如果我想把測試分類,應該怎樣實現呢?如果我想忽略某些測試,又應該如何去完成呢?不用擔心,NUnit已經有這樣的功能了.
下面我們一一作出回答.

SetUp/TearDown 屬性

在早期給的test fixture定義裡,我們說test fixture的測試是一組常規執行時資源.在測試完成之後,或是在測試執行種,或是釋放或清除之前,這些常規執行時資源在一確定的方式上可能需要獲取和初始化.NUnit使用2個額外的屬性:SetUpTearDown,就支援這種常規的初始化/清除.我們上面的例子來描述這個功能.讓我們增加乘法.
1using System;
2using NUnit.Framework;
3 
4namespace NUnitQuickStart
5{
6            [TestFixture]
7            public class NumersFixture
8            {
9                        [Test]
10                        public void AddTwoNumbers()
11                        {
12                                    int a=1;
13                                    int b=2;
14                                    int sum=a+b;
15                                    Assert.AreEqual(sum,3);
16                        }

17                        [Test]
18                        public void MultiplyTwoNumbers()
19                        {
20                                    int a = 1;
21                                    int b = 2;
22                                    int product = a * b;
23                                    Assert.AreEqual(2, product);
24                        }

25 
26            }

27}

28
   我們仔細一看,不對,有重複的程式碼,如何去除重複的程式碼呢?我們可以提取這些程式碼到一個獨立的方法,然後標誌這個方法為SetUp 屬性,這樣2個測試方法可以共享對運算元的初始化了,這裡是改動後的程式碼:
1using System;
2using NUnit.Framework;
3 
4namespace NUnitQuickStart
5{
6            [TestFixture]
7            public class NumersFixture
8            {
9                        private int a;
10                        private int b;
11                        [SetUp]
12                        public void InitializeOperands()
13                        {
14                                    a = 1;
15                                    b = 2;
16                        }

17 
18                        [Test]
19                        public void AddTwoNumbers()
20                        {
21                                    int sum=a+b;
22                                    Assert.AreEqual(sum,3);
23                        }

24                        [Test]
25                        public void MultiplyTwoNumbers()
26                        {
27                                    int product = a * b;
28                                    Assert.AreEqual(2, product);
29                        }

30 
31            }

32}

33
   這樣NUnit將在執行每個測試前執行標記SetUp屬性的方法.在本例中就是執行InitializeOperands()方法.記住,這裡這個方法必須為public,不然就會有以下錯誤:Invalid Setup or TearDown method signature

ExpectedException

這裡是一個驗證這個假設的測試.有的時候,我們知道某些操作會有異常出現,例如, 在例項中增加除法,某個操作被0除,丟擲的異常和.NET文件描述的一樣.參看以下原始碼.
1[Test] 2[ExpectedException(typeof(DivideByZeroException))] 3public void DivideByZero() 4{ 5int zero = 0; 6int infinity = a/zero; 7 Assert.Fail("Should have gotten an exception"); 8} 9
   除了[Test]屬性之外, DivideByZero方法有另外一個客戶屬性: ExpectedException.在這個屬性裡,你可以在執行過程中捕獲你期望的異常型別,例如在本例就是DivideByZeroException.如果這個方法在沒有丟擲期望異常的情況下完成了,這個測試失敗.使用這個屬性幫助我們寫程式設計師測試驗證邊界條件(Boundary Conditions).

Ignore 屬性

   由於種種原因,有一些測試我們不想執行.當然,這些原因可能包括你認為這個測試還沒有完成,這個測試正在重構之中,這個測試的需求不是太明確.但你有不想破壞測試,不然進度條可是紅色的喲.怎麼辦?使用Ignore屬性.你可以保持測試,但又不執行它們.讓我們標記MultiplyTwoNumbers測試方法為Ignore屬性:
1[Test] 2[Ignore("Multiplication is ignored")] 3public void MultiplyTwoNumbers() 4{ 5int product = a * b; 6 Assert.AreEqual(2, product); 7}
   執行測試,現在產生了下面的輸出(在圖5-1顯示):

圖 5-1: 在一個程式設計師測試中使用 Ignore屬性
   Ignore屬性可以附加到一個獨立的測試方法,也可以附加到整個測試類(TestFixture).如果Ignore屬性附加到TestFixture,所有在fixture的測試都被忽略.

TestFixtureSetUp/TestFixtureTearDown

   有時,一組測試需要的資源太昂貴.例如,資料庫連線可能是一個關鍵資源,在一個test fixture的每個測試中,開啟/關閉資料庫連線可能非常慢.這就是我在開始提到的問題.如何解決?NUnit有一對類似於前面討論的SetUp/TearDown的屬性: TestFixtureSetUp/TestFixtureTearDown.正如他們名字表明的一樣,這些屬性用來標記為整個test fixture初始化/釋放資源方法一次的方法.
   例如,如果你想為所有test fixture的測試共享相同的資料庫連線物件,我們可以寫一個開啟資料庫連線的方法,標記為TestFixtureSetUp屬性,編寫另外一個關閉資料庫連線的方法,標記為TestFixtureTearDown屬性.這裡是描述這個的例子.
1using NUnit.Framework; 2 3[TestFixture] 4public class DatabaseFixture 5{ 6 [TestFixtureSetUp] 7public void OpenConnection() 8{ 9//open the connection to the database 10 } 1112 [TestFixtureTearDown] 13public void CloseConnection() 14{ 15//close the connection to the database 16 } 1718 [SetUp] 19public void CreateDatabaseObjects() 20{ 21//insert the records into the database table 22 } 2324 [TearDown] 25public void DeleteDatabaseObjects() 26{ 27//remove the inserted records from the database table 28 } 2930 [Test] 31public void ReadOneObject() 32{ 33//load one record using the open database connection 34 } 3536 [Test] 37public void ReadManyObjects() 38{ 39//load many records using the open database connection 40 } 41} 4243

Test Suite

   Test Suite是test case或其他test suite的集合. 合成(Composite),模式描述了test case和test suite之間的關係.
參考來自NUnit的關於Suite的程式碼
Suite Attribute

1namespace NUnit.Tests
2{
3using System;
4  using NUnit.Framework;
5
6
7
8  public class AllTests
9  {
10    [Suite]
11    public static TestSuite Suite
12    {
13      get
14      {
15        TestSuite suite = new TestSuite("All Tests");
16        suite.Add(new OneTestCase());
17        suite.Add(new Assemblies.AssemblyTests());
18        suite.Add(new AssertionTest());
19        return suite;
20      }

21    }

22  }

23}

24
Category屬性
對於測試來說,你有的時候需要將之分類,此屬性正好就是用來解決這個問題的。
你可以選擇你需要執行的測試類目錄,也可以選擇除了這些目錄之外的測試都可以執行。在命令列環境裡 /include 和/exclude來實現。在GUI環境下,就更簡單了,選擇左邊工作域裡的Catagories Tab,選擇Add和Remove既可以了。
在上面的例子上做了一些改善,程式碼如下:
1using System; 2using NUnit.Framework; 3 4namespace NUnitQuickStart 5{ 6 [TestFixture] 7public class NumersFixture 8{ 9private int a; 10private int b; 11 [SetUp] 12public void InitializeOperands() 13{ 14 a = 1; 15 b = 2; 16 } 1718 [Test] 19 [Category("Numbers")] 20public void AddTwoNumbers() 21{ 22int sum=a+b; 23 Assert.AreEqual(sum,3); 24 } 2526 [Test] 27 [Category("Exception")] 28 [ExpectedException(typeof(DivideByZeroException))] 29public void DivideByZero() 30{ 31int zero = 0; 32int infinity = a/zero; 33 Assert.Fail("Should have gotten an exception"); 34 } 35 [Test] 36 [Ignore("Multiplication is ignored")] 37 [Category("Numbers")] 38public void MultiplyTwoNumbers() 39{ 40int product = a * b; 41 Assert.AreEqual(2, product); 42 } 4344 } 45
NUnit-GUI介面如圖5-2:
 
圖5-2:使用Catagories屬性的介面

Explicit屬性

本屬性忽略一個test和test fixture,直到它們顯式的選擇執行。如果test和test fixture在執行的過程中被發現,就忽略他們。所以,這樣一來進度條顯示為黃色,因為有test或test fixture忽略了。
例如:
 
1
2                        [Test,Explicit]
3                        [Category("Exception")]
4                        [ExpectedException(typeof(DivideByZeroException))]
5                        public void DivideByZero()
6                        {
7                                    int zero = 0;
8                                    int infinity = a/zero;
9                                    Assert.Fail("Should have gotten an exception");
10                        }

11
    為什麼會設計成這樣呢?原因是Ingore屬性忽略了某個test或test fixture,那麼他們你再想呼叫執行是不可能的。那麼萬一有一天我想呼叫被忽略的test或test fixture怎麼辦,就用Explicit屬性了。我想這就是其中的原因吧。

Expected Exception屬性

  期望在執行時丟擲一個期望的異常,如果是,則測試通過,否則不通過。
參看下面的例子:
1[Test]
2[ExpectedException(typeofInvalidOperationException))]
3public void ExpectAnException()
4{
5   int zero = 0;
6   int infinity = a/zero;
7   Assert.Fail("Should have gotten an exception");
8                      
9 }

10
    在本測試中,應該丟擲DivideByZeroException,但是期望的是InvalidOperationException,所以不能通過。如果我們將[ExpectedException(typeof(InvalidOperationException))]改為[ExpectedException(typeof(DivideByZeroException))],本測試通過。

5 . 測試生命週期合約

   如果記得test case的定義,其中一個屬性是測試的獨立性或隔離性.SetUp/TearDown方法提供達到測試隔離性的目的.SetUp確保共享的資源在每個測試執行前正確初始化,TearDown確保沒有執行測試產生的遺留副作用. TestFixtureSetUp/TestFixtureTearDown同樣提供相同的目的,但是卻在test fixture範圍裡,我們剛才描述的內容組成了測試框架的執行時容器(test runner)和你寫的測試之間的生命週期合約(life-cycle contract).
   為了描述這個合約,我們寫一個簡單的測試來說明什麼方法呼叫了,怎麼合適呼叫的.這裡是程式碼:
1using System; 2using NUnit.Framework; 3[TestFixture] 4public class LifeCycleContractFixture 5{ 6 [TestFixtureSetUp] 7public void FixtureSetUp() 8{ 9 Console.Out.WriteLine("FixtureSetUp"); 10 } 1112 [TestFixtureTearDown] 13public void FixtureTearDown() 14{ 15 Console.Out.WriteLine("FixtureTearDown"); 16 } 1718 [SetUp] 19public void SetUp() 20{ 21 Console.Out.WriteLine("SetUp"); 22 } 2324 [TearDown] 25public void TearDown() 26{ 27 Console.Out.WriteLine("TearDown"); 28 } 2930 [Test] 31public void Test1() 32{ 33 Console.Out.WriteLine("Test 1"); 34 } 3536 [Test] 37public void Test2() 38{ 39 Console.Out.WriteLine("Test 2"); 40 } 4142} 4344
當編譯和執行這個測試,可以在System.Console視窗看到下面的輸出:
FixtureSetUp SetUp Test 1 TearDown SetUp Test 2 TearDown FixtureTearDown
   可以看到, SetUp/TearDown方法呼叫在每個測試方法的前後. 整個fixture呼叫一次TestFixtureSetUp/TestFixtureTearDown方法.
 
下載:
1)NUnit的應用文件 下載
2)本文的PDF版 下載
3)本文的原始碼 下載

相關文章