首先什麼是TDD呢?Kent Beck在他的<<測試驅動開發 >>(Addison-Wesley Professional,2003)一書中,使用下面2個原則來定義TDD:
· 除非你有一個失敗的自動測試,永遠不要寫一單行代碼.
· 阻止重複
我想第一個原則是顯而易見的.在沒有失敗的自動測試下就不要寫代碼.因為測試是嵌入在代碼必須滿足的需求中.如果沒有需求,就沒有必要實作任何東西.是以這個原則阻止我們去實作那些沒有測試和在解決方案中不需要的功能.
第二個原則說明了在一個程式中,不應該包含重複的代碼.如果代碼重複,我想這就是不好的軟體設計的象征.随着時間的流逝,它會對程式造成不一緻的問題,并且使代碼變非常混亂 ,因為我們時常不會記得重複代碼的位置.如果發現代碼重複,我想我們應該立即删除代碼重複.其實這就涉及到重構了.在這裡我就不多講了.
一般來說,測試分為2種類型,一是程式員自己的測試,另外一種是客戶的測試.關于客戶測試,我推薦一個FIT的架構,非常不錯。在這裡,我們講的TDD就是程式員測試.那麼什麼是程式員測試呢?我認為就是我們常說的單元測試.既然是單元測試,在.NET裡勢必會用到某些工具,目前最著名恐怕就是我即将介紹的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/去下載下傳.
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輸出流的文本消息。
接下來,我将講述這個架構如何使用.同時也涉及到一些非常重要的概念,我想其客戶屬性是非常重要的.在NUnit裡,有以下幾種屬性:
Test Fixture
Test
下面我将對每種屬性一一講解.
本屬性标記一個類包含測試,當然setup和teardown方法可有可無.(關于setup 和teardown方法在後面介紹)
做為一個測試的類,這個類還有一些限制
必須是Public,否則NUnit看不到它的存在.
它必須有一個預設的構造函數,否則是NUnit不會構造它.
構造函數應該沒有任何副作用,因為NUnit在運作時經常會構造這個類多次,如果要是構造函數要什麼副作用的話,那不是亂了.
舉個例子
1
using System;
2
using NUnit.Framework;
3
namespace MyTest.Tests
4
{
5
6
[TestFixture]
7
public class PriceFixture
8
9
//
10
}
11
}
12
Test屬性用來标記一個類(已經标記為TestFixture)的某個方法是可以測試的.為了和先前的版本向後相容,頭4個字元(“test”)忽略大小寫.(參看http://nunit.org/test.html)
這個測試方法可以定義為:
public void MethodName()
從上面可以看出,這個方法沒有任何參數,其實測試方法必須沒有參數.如果我們定義方法不對的話,這個方法不會出現在測試方法清單中.也就是說在NUnit的界面左邊的工作域内,看不到這個方法.還有一點就是這個方法不傳回任何參數,并且必須為Public.
例如:
using System;
using NUnit.Framework;
namespace MyTest.Tests
public class SuccessTests
[Test] public void Test1()
{ /**//*
*/ }
13
14
我将舉個例子,一步一步示範如何去使用NUnit.
在Microsoft Visual Studio .NET中,讓我們開始建立一個新的工程。選擇Visual C#工程作為工程類型,Class Library作為模闆。将工程命名為NUnitQuickStart.圖4-1是一個描述本步驟的Visual Studio .NET。
圖 4-1: 建立第一個NUnit工程
在Microsoft Visual Studio .NET裡建立這個例子時,你需要增加一個nunit.framework.dll引用,如下:
在Solution Explorer右擊引用,然後選擇增加引用
nunit.framework元件,在Add Reference對話框中按Select和OK按鈕。
圖 4-2: 增加一個 nunit.framework.dll 引用到工程
為工程加一個NumbersFixture類。這裡是這個例子的代碼。
using System;
using NUnit.Framework;
namespace NUnitQuickStart
{
[TestFixture]
public class NumersFixture
[Test]
public void AddTwoNumbers()
int a=1;
int b=2;
int sum=a+b;
15
Assert.AreEqual(sum,3);
16
}
17
}
18
19
從程式->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:将NUnit-Gui 作為工程的測試運作器
現在編譯solution。成功編譯後,開始應用程式。NUnit-Gui測試運作器出現。當你第一次開始NUnit-Gui,它打開時沒有測試加載。從File菜單選擇Oprn,浏覽NUnitQuickStart.dll的路徑。當你加載了測試的程式集,測試運作器為加載的程式集的測試産生一個可見的表現。在例子中,測試程式集僅有一個測試,測試程式集的結構如圖4-4所示:
圖 4-4: 測試程式集的測試在 NUnit-Gui中的視圖
按Run按鈕。樹的節點變為綠色,而且測試運作器視窗上的進度條變綠,綠色代表成功通過。
上面的例子介紹了基本的NUnit特性和功能. TestFixture, Test, 和 Assert是3個最基本的特征,我們可以用這些特性進行程式員測試了.但是有的時候,你覺得這3個遠遠不夠,比如有的時候打開一個資料庫連接配接多次,有沒有隻讓它打開一次的方法呢?如果我想把測試分類,應該怎樣實作呢?如果我想忽略某些測試,又應該如何去完成呢?不用擔心,NUnit已經有這樣的功能了.
下面我們一一作出回答.
在早期給的test fixture定義裡,我們說test fixture的測試是一組正常運作時資源.在測試完成之後,或是在測試執行種,或是釋放或清除之前,這些正常運作時資源在一确定的方式上可能需要擷取和初始化.NUnit使用2個額外的屬性:SetUp 和TearDown,就支援這種正常的初始化/清除.我們上面的例子來描述這個功能.讓我們增加乘法.
public void MultiplyTwoNumbers()
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個測試方法可以共享對操作數的初始化了,這裡是改動後的代碼:
private int a;
private int b;
[SetUp]
public void InitializeOperands()
a = 1;
b = 2;
29
30
31
32
33
這裡是一個驗證這個假設的測試.有的時候,我們知道某些操作會有異常出現,例如, 在執行個體中增加除法,某個操作被0除,抛出的異常和.NET文檔描述的一樣.參看以下源代碼.
1
[Test]
2
[ExpectedException(typeof(DivideByZeroException))]
3
public void DivideByZero()
4
5
int zero = 0;
6
int infinity = a/zero;
7
Assert.Fail("Should have gotten an exception");
8
9
除了[Test]屬性之外, DivideByZero方法有另外一個客戶屬性: ExpectedException.在這個屬性裡,你可以在執行過程中捕獲你期望的異常類型,例如在本例就是DivideByZeroException.如果這個方法在沒有抛出期望異常的情況下完成了,這個測試失敗.使用這個屬性幫助我們寫程式員測試驗證邊界條件(Boundary Conditions).
由于種種原因,有一些測試我們不想運作.當然,這些原因可能包括你認為這個測試還沒有完成,這個測試正在重構之中,這個測試的需求不是太明确.但你有不想破壞測試,不然進度條可是紅色的喲.怎麼辦?使用Ignore屬性.你可以保持測試,但又不運作它們.讓我們标記MultiplyTwoNumbers測試方法為Ignore屬性:
[Ignore("Multiplication is ignored")]
public void MultiplyTwoNumbers()
int product = a * b;
Assert.AreEqual(2, product);
運作測試,現在産生了下面的輸出(在圖5-1顯示):
圖 5-1: 在一個程式員測試中使用 Ignore屬性
Ignore屬性可以附加到一個獨立的測試方法,也可以附加到整個測試類(TestFixture).如果Ignore屬性附加到TestFixture,所有在fixture的測試都被忽略.
有時,一組測試需要的資源太昂貴.例如,資料庫連接配接可能是一個關鍵資源,在一個test fixture的每個測試中,打開/關閉資料庫連接配接可能非常慢.這就是我在開始提到的問題.如何解決?NUnit有一對類似于前面讨論的SetUp/TearDown的屬性: TestFixtureSetUp/TestFixtureTearDown.正如他們名字表明的一樣,這些屬性用來标記為整個test fixture初始化/釋放資源方法一次的方法.
例如,如果你想為所有test fixture的測試共享相同的資料庫連接配接對象,我們可以寫一個打開資料庫連接配接的方法,标記為TestFixtureSetUp屬性,編寫另外一個關閉資料庫連接配接的方法,标記為TestFixtureTearDown屬性.這裡是描述這個的例子.
[TestFixture]
public class DatabaseFixture
[TestFixtureSetUp]
public void OpenConnection()
//open the connection to the database
}
[TestFixtureTearDown]
public void CloseConnection()
//close the connection to the database
[SetUp]
public void CreateDatabaseObjects()
//insert the records into the database table
[TearDown]
public void DeleteDatabaseObjects()
//remove the inserted records from the database table
[Test]
public void ReadOneObject()
//load one record using the open database connection
34
35
36
37
public void ReadManyObjects()
38
39
//load many records using the open database connection
40
41
42
43
Test Suite是test case或其他test suite的集合. 合成(Composite),模式描述了test case和test suite之間的關系.
參考來自NUnit的關于Suite的代碼
Suite Attribute
namespace NUnit.Tests
public class AllTests
[Suite]
public static TestSuite Suite
get
TestSuite suite = new TestSuite("All Tests");
suite.Add(new OneTestCase());
suite.Add(new Assemblies.AssemblyTests());
suite.Add(new AssertionTest());
return suite;
}
}
Category屬性
對于測試來說,你有的時候需要将之分類,此屬性正好就是用來解決這個問題的。
你可以選擇你需要運作的測試類目錄,也可以選擇除了這些目錄之外的測試都可以運作。在指令行環境裡 /include 和/exclude來實作。在GUI環境下,就更簡單了,選擇左邊工作域裡的Catagories Tab,選擇Add和Remove既可以了。
在上面的例子上做了一些改善,代碼如下:
public void InitializeOperands()
[Category("Numbers")]
[Category("Exception")]
[ExpectedException(typeof(DivideByZeroException))]
public void DivideByZero()
int zero = 0;
int infinity = a/zero;
Assert.Fail("Should have gotten an exception");
[Ignore("Multiplication is ignored")]
44
}
45
NUnit-GUI界面如圖5-2:
本屬性忽略一個test和test fixture,直到它們顯式的選擇執行。如果test和test fixture在執行的過程中被發現,就忽略他們。是以,這樣一來進度條顯示為黃色,因為有test或test fixture忽略了。
例如:
[Test,Explicit]
[Category("Exception")]
}
為什麼會設計成這樣呢?原因是Ingore屬性忽略了某個test或test fixture,那麼他們你再想調用執行是不可能的。那麼萬一有一天我想調用被忽略的test或test fixture怎麼辦,就用Explicit屬性了。我想這就是其中的原因吧。
期望在運作時抛出一個期望的異常,如果是,則測試通過,否則不通過。
參看下面的例子:
[Test]
[ExpectedException(typeofInvalidOperationException))]
public void ExpectAnException()
int zero = 0;
int infinity = a/zero;
Assert.Fail("Should have gotten an exception");
}
在本測試中,應該抛出DivideByZeroException,但是期望的是InvalidOperationException,是以不能通過。如果我們将[ExpectedException(typeof(InvalidOperationException))]改為[ExpectedException(typeof(DivideByZeroException))],本測試通過。
如果記得test case的定義,其中一個屬性是測試的獨立性或隔離性.SetUp/TearDown方法提供達到測試隔離性的目的.SetUp確定共享的資源在每個測試運作前正确初始化,TearDown確定沒有運作測試産生的遺留副作用. TestFixtureSetUp/TestFixtureTearDown同樣提供相同的目的,但是卻在test fixture範圍裡,我們剛才描述的内容組成了測試架構的運作時容器(test runner)和你寫的測試之間的生命周期合約(life-cycle contract).
為了描述這個合約,我們寫一個簡單的測試來說明什麼方法調用了,怎麼合适調用的.這裡是代碼:
public class LifeCycleContractFixture
public void FixtureSetUp()
Console.Out.WriteLine("FixtureSetUp");
public void FixtureTearDown()
Console.Out.WriteLine("FixtureTearDown");
public void SetUp()
Console.Out.WriteLine("SetUp");
public void TearDown()
Console.Out.WriteLine("TearDown");
public void Test1()
Console.Out.WriteLine("Test 1");
public void Test2()
Console.Out.WriteLine("Test 2");
當編譯和運作這個測試,可以在System.Console視窗看到下面的輸出:
FixtureSetUp
SetUp
Test 1
TearDown
Test 2
FixtureTearDown
可以看到, SetUp/TearDown方法調用在每個測試方法的前後. 整個fixture調用一次TestFixtureSetUp/TestFixtureTearDown方法.
部落格園大道至簡
<a href="http://www.cnblogs.com/jams742003/" target="_blank">http://www.cnblogs.com/jams742003/</a>
轉載請注明:部落格園