一、簡介
NUnit是一款堪與JUnit齊名的開源的回歸測試框架,供.net開發人員做單元測試之用,可以從www.nunit.org網站上免費獲得,最新版本2.2.6。NUnit 2.2.6有5個下載文件,這裏用的是NUnit-2.2.6-net-1.1.msi。下載後雙擊該文件,然後按提示進行安裝,這樣系統中就具備NUnit環境了。
二、配置類庫
這次我選擇的IDE是Borland的Delphi 2006(正確的叫法應該是Borland Developer Studio 2006,以下簡稱Delphi)。打開後點擊菜單“File”->“New”->“Other”,打開“New Items”對話框:
在該對話框中,選擇“C# Projects”或“Delphi for .NET Projects”,雖然兩者在操作上會稍有差異,但其實相差並不很大。然後選擇其中的“Console Application”,點擊“OK”按鈕。如果選擇的是C#,那麽會彈出一個“New Application”對話框:
請在其中設置項目名稱和保存位置,我的項目名稱爲NUnitCS,位置爲本機的G:\MDZPCK\Borland\MySY\NUnitCS,設置好後,點擊“OK”按鈕。如果選擇的是Delphi,那麽跟以前版本的Delphi一樣,項目會直接被創建,而不彈出任何對話框。此時請按下快捷鍵Ctrl + S,打開Save對話框,對項目進行保存,這是一個良好的習慣。我將自己的項目文件名稱設爲NUnitOP.bdsproj(因爲Delphi使用的是Object Pascal語言嘛),該文件名就是項目名稱,保存路徑爲G:\MDZPCK\Borland\MySY\NUnitOP。
項目創建後,點擊菜單“Project”->“Add Reference”,打開“Add Reference”對話框:
在“.NET Assembles”選項卡中找到組件名稱爲nunit.framework的一項,雙擊添加到“New References”中,別的組件不用管,然後點擊“OK”按鈕,此時在項目中就可以使用NUnit類庫了。
三、編寫用于測試的類
用于測試的類很簡單,名爲Book,只有id和name兩個屬性,這兩個屬性將分別用于兩個用例當中。
下面開始編寫,請點擊菜單“File”->“New”->“Other”,打開“New Items”對話框:
在該對話框中選擇“C# Projects”或“Delphi for .NET Projects”下的“New Files”,然後選中“Class”,點擊“OK”按鈕。此時Class文件雖然在工程中已生成,但尚未保存在硬盤上,所以請先按下快捷鍵Ctrl + S,我將文件命名爲Book.cs和Book.pas。這裏插一句,我感覺Delphi在文件保存這方面做的很差勁,如果沒有經常保存文件的習慣,搞不好真的會出問題。
類創建後,需要修改代碼,下邊是C#代碼:
using System;
namespace NUnitCS
{
/// <summary>
/// Summary description for Book.
/// </summary>
public class Book
{
private string pid = null;
private string pname = null;
public string id
{
get
{
return pid;
}
set
{
pid = value;
}
}
public string name
{
get
{
return pname;
}
set
{
pname = value;
}
}
}
}
沒什麽可說的吧?下邊是Delphi代碼:
unit Book;
interface
type
TBook = class
private
pid : string;
pname : string;
function GetId(): string;
procedure SetId(value: string);
function GetName(): string;
procedure SetName(value: string);
public
constructor Create;
property id : string read GetId write SetId;
property name : string read GetName write SetName;
end;
implementation
constructor TBook.Create;
begin
inherited Create;
end;
function TBook.GetId(): string;
begin
GetId := pid;
end;
procedure TBook.SetId(value: string);
begin
pid := value;
end;
function TBook.GetName(): string;
begin
GetName := pname;
end;
procedure TBook.SetName(value: string);
begin
pname := value;
end;
end.
可以看到,和以前版本的Delphi代碼相差不多,都是固定格式。這裏可能引起費解的有兩點:一個是屬性,在Delphi中定義屬性必須先爲這個屬性聲明一個作爲get訪問器的函數和一個作爲set訪問器的過程,並實現之,然後使用property語句來定義屬性,並將get訪問器和set訪問器與這兩個函數和過程相關聯。注意,這裏建議將函數和過程聲明在private部分,這樣在調用時,就會只看到屬性,而不會看到這兩個函數和過程了。另一個費解的問題是,Book是類型還是命名空間。其實Book是命名空間,TBook才是類型,這種情況與Delphi文件的格式有關。在Delphi中,每建立一個文件都會相應建立一個命名空間,如果想讓多個類在一個命名空間下,就需要把這幾個類建在一個文件中,但這不符合Delphi的編碼風格。最後,別忘了定義一個構造函數,Delphi是不會默認提供的。
至此,用于測試的類編寫完成了。
四、編寫測試用例
這裏只用了一個類進行測試,名爲BookTest,以前這樣的類可能需要繼承NUnit.Framework.TestCase類,但現在只需要對該類使用TestFixture屬性進行標識即可,而無須繼承了。BookTest類包含兩個用例,分別對應該類的testId和testName方法,即每個方法實現了一個測試用例。注意,在NUnit中,這些用來實現測試用例的方法有兩種手段進行標識:一個是以testXXX的格式來命名,一個是使用Test屬性進行標識。此外,BookTest還有Init和Dispose這兩個方法,並分別使用SetUp和TearDown屬性來進行標識,前者在每個測試方法開始之前執行,多用來做初始化;後者在每個測試方法完成之後執行,多用來清理資源。注意,這兩個方法的名稱並沒有什麽限制,但必須用SetUp和TearDown屬性進行標識。另外,NUnit還提供了TestFixtureSetUp和TestFixtureTearDown屬性,功能與SetUp和TearDown類似,但前者是在所有用例執行之前做初始化、之後做清理,而後者是在每個用例執行之前做初始化、之後做清理。下面開始編寫BookTest。
點擊菜單“File”->“New”->“Other”,打開“New Items”對話框,在該對話框中選擇“C# Projects”或“Delphi for .NET Projects”下的“New Files”,然後選中“Class”,點擊“OK”按鈕。此時請按下快捷鍵Ctrl + S,保存文件,我將文件命名爲BookTest.cs和BookTest.pas。下面修改代碼,C#代碼如下:
using System;
using NUnit.Framework;
namespace NUnitCS
{
/// <summary>
/// Summary description for Class1.
/// </summary>
[TestFixture]
public class BookTest
{
Book book = null;
[TestFixtureSetUp]
public void Init()
{
Console.WriteLine("測試開始!");
book = new Book();
Console.WriteLine("book對象被初始化!");
}
[Test]
public void testId()
{
book.id = "001"; //設置id屬性的值爲001
//使用Assert查看id屬性的值是否爲001
Assert.AreEqual("001", book.id);
Console.WriteLine("id屬性被測試!");
}
[Test]
public void testName()
{
book.name = "ASP"; //設置name屬性的值爲ASP
//使用Assert查看name屬性的值是否爲JSP,這是個必然出現錯誤的測試
Assert.AreEqual("JSP", book.name);
Console.WriteLine("name屬性被測試!");
}
[TestFixtureTearDown]
public void Dispose()
{
Console.WriteLine("book對象將被清理!");
book = null;
Console.WriteLine("測試結束!");
}
}
}
這裏Init和Dispose方法沒什麽好說的,就是執行了對book對象的初始化和清理,不過testId和testName需要說明一下。前者是在對book的id屬性進行測試,首先賦值爲”001”,然後使用Assert的AreEqual方法查看id屬性中存放的值是否是期待的值,由于我的期待值也是”001”,所以執行後這個用例應該是成功的;後者則是對book的name屬性進行測試,也是首先賦值爲”ASP”,然後使用Assert的AreEqual方法查看其值是否是期待的,由于我特意將期待值設定爲根本不可能的”JSP”,因此這個用例執行後會出現一個錯誤。但請注意,由于我是特意要讓測試出現錯誤,所以將期待值設定成了不可能的值,如果你是測試人員,請千萬不要這麽做,否則如果別的地方導致了錯誤,很容易給自己造成不必要的麻煩。
下面簡單介紹一下上邊用到的靜態類NUnit.Framework.Assert。該類主要包含20個方法:
1.AreEqual()和AreNotEqual()方法,用來查看兩個對象的值是否相等或不等,與對象比較中使用的Equals()方法類似。
2.AreSame()和AreNotSame()方法,用來比較兩個對象的引用是否相等或不等,類似于通過“Is”或“==”比較兩個對象。
3.Contains()方法,用來查看對象是否在集合中,集合類型應與System.Collections.IList兼容。示例:
object o = new object();
ArrayList al = new ArrayList();
al.Add(o);
Assert.Contains(o, al);
4.Greater()和Less()方法,用來比較兩個數值的大小,前者相當于大于號(>),後者相當于小于號(<)。
5.IsInstanceOfType()和IsNotInstanceOfType()方法,用來判斷對象是否兼容于指定類型。示例:
Type t = new object().GetType();
string s = "";
Assert.IsInstanceOfType(t, s);
由于Object是.net中所有類型的基類,String類型兼容于Object,因此這個示例是能夠運行通過的。而下邊這個示例運行將是失敗的:
Type t = new ArrayList().GetType();
string s = "";
Assert.IsInstanceOfType(t, s);
6.IsAssignableFrom()和IsNotAssignableFrom()方法,用來判斷對象是否是指定類型的實例。示例:
Type t = new object().GetType();
string s = "";
Assert.IsAssignableFrom(t, s);
這個示例與之前的示例是一樣的,但由于字符串s不是Object類型的,因此無法運行通過。而下邊這個實例可以運行通過:
Type t = new string("").GetType();
string s = "";
Assert.IsAssignableFrom(t, s);
7.IsFalse()和IsTrue()方法,用來查看變量是是否爲false或true,如果IsFalse()查看的變量的值是false則測試成功,如果是true則失敗,IsTrue()與之相反。
8.IsNull()和IsNotNull()方法,用來查看對象是否爲空和不爲空。
9.IsEmpty()和IsNotEmpty()方法,用來判斷字符串或集合是否爲空串或沒有元素,其中集合類型應與ICollection兼容。
10.IsNaN()方法,用來判斷指定的值是否不是數字。
11.Fail()方法,意爲失敗,用來抛出錯誤。我個人認爲有兩個用途:首先是在測試驅動開發中,由于測試用例都是在被測試的類之前編寫,而寫成時又不清楚其正確與否,此時就可以使用Fail方法抛出錯誤進行模擬;其次是抛出意外的錯誤,比如要測試的內容是從數據庫中讀取的數據是否正確,而導致錯誤的原因卻是數據庫連接失敗。
12.Ignore()方法,意爲忽略,用來忽略後續代碼的執行,用途可以參考Fail()方法。
此外,NUnit還提供了一個專用于字符串的靜態類NUnit.Framework. StringAssert,該類主要包含4個方法:
1.Contains()方法,用來查看指定的第二個字符串中是否包含了第一個字符串。
2.StartsWith ()和EndsWith ()方法,分別用來查看指定的第一個字符串是否位于第二個字符串的開頭和結尾。
3.AreEqualIgnoringCase()方法,用來比較兩個字符串是否相等。
下面再看一下Delphi代碼:
unit BookTest;
interface
uses
Book,
NUnit.Framework;
type
[TestFixture]
TBookTest = class
book : TBook;
private
{ Private Declarations }
public
constructor Create;
[TestFixtureSetUp]
procedure Init();
[Test]
procedure testId();
[Test]
procedure testName();
[TestFixtureTearDown]
procedure Dispose();
end;
implementation
constructor TBookTest.Create();
begin
inherited Create;
end;
procedure TBookTest.Init();
begin
onsole.WriteLine('測試開始!');
book := TBook.Create();
Console.WriteLine('book對象被初始化!');
end;
procedure TBookTest.testId();
begin
book.id := '001'; //設置id屬性的值爲001
//使用Assert查看id屬性的值是否爲001
Assert.AreEqual('001', book.id);
Console.WriteLine('id屬性被測試!');
end;
procedure TBookTest.testName();
begin
book.name := 'ASP'; //設置name屬性的值爲ASP
//使用Assert查看name屬性的值是否爲JSP,這是個必然出現錯誤的測試
Assert.AreEqual('JSP', book.name);
Console.WriteLine('name屬性被測試!');
end;
procedure TBookTest.Dispose();
begin
Console.WriteLine('book對象將被清理!');
book := nil;
Console.WriteLine('測試結束!');
end;
end.
也是固定格式,沒什麽好說的吧?改好後,點擊菜單“Run”->“Run”或按F9鍵運行程序。等等,main函數裏頭好象一句代碼也沒寫過呢吧?沒錯,一句也沒寫,但如果你用的是C#照做就可以了,如果用的是Delphi,那麽請把NUnitOP.bdsproj文件打開。我們需要對該文件進行修改,否則程序雖能生成,但卻無法用于測試。什麽,沒有這個文件?按Ctrl + F12,在打開的“View Unit”對話框中選擇NUnitOP一項,然後點擊“OK”。在文件中修改代碼如下:
program NUnitOP;
{$APPTYPE CONSOLE}
{%DelphiDotNetAssemblyCompiler 'e:\program files\nunit 2.2\bin\nunit.framework.dll'}
{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.dll'}
{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.Data.dll'}
{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\System.XML.dll'}
uses
SysUtils,
Book in 'Book.pas',
BookTest in 'BookTest.pas';
var
bt : TBookTest;
begin
bt := TBookTest.Create();
end.
修改好後,按F9鍵運行程序。在看到黑屏一閃之後,編碼工作完成。
五、運行NUnit
編碼完成後,就可以使用NUnit測試了。NUnit有兩種界面,一種是命令行的,一種是可視化的,我使用的就是後者。點擊“開始”菜單->“所有程序”->“NUnit 2.2.6”->“NUnit-Gui”,打開NUnit的可視化界面:
點擊菜單“File”->“Open”,打開剛才運行之後生成的可執行文件:
此時就可以使用BookTest類對Book類進行測試了。請首先選擇testId,點擊“Run”按鈕,運行結果如下圖:
testId前的灰點變綠,而且進度條顯示爲綠條,這表明運行成功。下面再選擇TBookTest,點擊“Run”按鈕,運行結果如下圖:
testId前的點依然是綠色,但testName前的點是紅色,而且進度條顯示爲紅條,這表明testName中存在錯誤。不過這個錯誤是預計之內的,如果不想看到,可以在Delphi中將testName()方法中的”JSP”改成”ASP”,然後重新運行。此時無須重新啓動NUnit,NUnit會自動加載重新編寫好的文件。此時再運行BookTest,進度條已不是紅色,而是綠色了。
六、說明
本文是對《NUnit學習筆記之Delphi 2005篇》的修正,NUnit和Delphi都使用了新版本。NUnit 2.2.6對2.2.0進行了很多擴展,這從對NUnit.Framework.Assert和NUnit.Framework.StringAssert的介紹就可以看出來。
其實以前感覺Delphi語法挺優美的,不過自從進入.net時代,就怎麽看怎麽不爽了。盡管也清楚,這是爲了讓Delphi程序員可以輕松進入.net世界,Borland也付出了很多勞動,而且Delphi還是我最最崇拜的Anders的傑作,但還是感覺別扭。看來人生經常也挺諷刺的。