
Delphi單元測試工具Dunit介紹
廣州邦訊科技 測試部 謝慧強
Dunit基本介紹
Dunit是Xunit家族中的一員,用于Dephi的單元測試。是Extreme Programming測試實現Xtreme Testing的一種工具。Dunit是一個Free的測試工具,沒有代碼覆蓋率功能。
Dunit的官方Web Site 是https://sourceforge.net/projects/dunit/。
使用Dunit應該先看看Dunit安裝目錄下的doc\README.html。本文也是參看Readme寫的。
配置測試環境
在使用Dunit前應該將下載的Dunit解壓。然后后將Dunit的路徑加到菜單 Tools->Environment Options 里面的Library->Library Path中。

Dunit的主要文件
File |
Description |
TestFramework.pas |
The framework itself. |
TestExtensions.pas |
Decorator classes that may be used to extend test cases. |
GUITesting.pas |
Classes for testing user interfaces (Forms and Dialogs). |
TextTestRunner.pas |
Routines to run tests in console mode. |
GUITestRunner.pas |
The graphical user interface to the framework. |
GUITestRunner.dfm |
The GUITestRunner Form |
Dunit基本實現方法(GUI方式)
Dunit的基本實現思路是將被測試代碼(單元)與測試代碼(單元)分開。提供一個FrameWork及一個運行界面。 所有的測試單元都應繼承TtestCase。
運行GUI界面

運行TestCase
這里要注意的一點是SetUp方法和TearDown是每個測試方法運行時都被調用的,如果想要只運行一次Setup及TearDown,應該使用TtestSetup類,具體情況后面《Dunit附加功能》一節。
創建一個簡單的例子
創建一個被測試的Project
創建一個名為BeTestProject的Project,將確省的Unit1保存為BeTestUnit.pas文件。把確省的TForm1改名為BeTestForm中增加一個Public的函數BeTestFunction,BeTestFunction代碼如下:
function BeTestForm.BeTestFunction(i,j:integer):integer;
begin
Result:=i*j;
end;
創建一個測試Project
創建新的Project
再創建一個Project,命名為TestProject。如果沒有和BeTestProject放在同一目錄,將BeTestProject的存放路徑加到加到菜單 Tools->Environment Options 里面的Library->Library Path中。
編寫TestCase
刪除確省的Unit1(Form1),創建一個的Unit,注意不是Form.

將創建的Unit保存為TestUnit,在interface中加入以下代碼
uses
TestFrameWork,BeTestUnit;
TestFrameWork是每個TestCase都必須使用的,后面要使用的TtestCase等類的定義都在TestFrameWork中。BeTestUnit是將要被測試單元。
定義TestCase,測試類定義代碼如下:
TTestCaseFirst = class(TTestCase)
private
BeTestForm : TBeTestForm; //要測試的類
protected
procedure SetUp; override; //初始化類
procedure TearDown; override; //清除數據
published
procedure TestFirst; //第一個測試方法
procedure TestSecond; //第二個測試方法
end;
在定義測試方法時候注意,Dunit是通過RTTI(RunTime Type Information)來尋找并自動注冊測試方面的,具體實現是通過代碼
TestFramework.RegisterTest(TTestCaseFirst.Suite);
這段代碼將在后面提到,TtestCaseFirst.Suit在尋找的規則是:
1、測試方法是沒有參數的Procedure
2、測試方法被申明為Published
SetUp,TearDown是在運行測試方法前、后運行的,所有一般把要測試的類的初始化及清除放在這兩個過程中。
以下是實現的代碼:
procedure TTestCaseFirst.SetUp;
begin
BeTestForm := TBeTestForm.Create(Nil);
end;
procedure TTestCaseFirst.TearDown;
begin
BeTestForm.Destroy;
end;
procedure TTestCaseFirst.TestFirst; //第一個測試方法
begin
Check(BeTestForm.BeTestFunction(1,3) = 3,'First Test fail');
end;
procedure TTestCaseFirst.TestSecond; //第二個測試方法
begin
Check(BeTestForm.BeTestFunction(1,3)=4,'Second Test fail');
end;
//Register TestCase
initialization
TestFramework.RegisterTest(TTestCaseFirst.Suite);
end.
Check是TestCase類提供的一個方法。以下是TestCase的實現代碼:
procedure TTestCase.Check(condition :boolean; msg :string);
begin
if (not condition) then
Fail(msg, CallerAddr);
End;
如果Check沒有通過的話,Dunit將報錯。錯誤提示就在第二個參數中定義,其他有關類及方法的定義請看連機文檔,文檔放在
Dunit安裝目錄\doc\API\IDH_Library_DUnit_-_Xtreme_Unit_Testing_for_Delphi.htm
Initialzation代碼完成測試單元的注冊。
修改Project主文件
運行前的最后一步是修改Project主文件TestProject.dpr。先使用菜單Project->View Source打開TestProject.dpr.
修改后的代碼如下:
program TestProject;
uses
Forms,
TestFrameWork,
GUITestRunner,
TestUnit in 'TestUnit.pas';
{$R *.res}
begin
Application.Initialize;
//Application.Run;
GUITestRunner.RunRegisteredTests;
end.
上面的加粗代碼是要增加和修改。
運行測試例子
運行的測試結果如下:
使用TestSuite
使用TestSuite的目的是對TestCase進行分類管理,如果我們再增加一個TestCase 如下
TTestCaseSecond = class(TTestCase)
published
procedure TestThrid;
end;
添加TestThrid實現代碼后,在initialization代碼處增加
TestFramework.RegisterTest(TTestCaseSecond.Suite);
運行以后我們可以看到結果如下:

如果我們將initialization處的代碼改為如下:
initialization
TestFramework.RegisterTest('Simple suite',TTestCaseFirst.Suite);
TestFramework.RegisterTest('Simple suite',TTestCaseSecond.Suite);
end.
那么運行的結果如下:

這就是一個簡單的TestSuite的使用,我們將TestCaseFirst和TestCaseSecond放到Simple suite中來進行管理。
對于復雜的應用,我們也可以使用多層的TestSuite來進行管理。先增加一個函數:
function UnitTests: ITestSuite;
var
ATestSuite,BTestSuite: TTestSuite;
begin
BTestSuite := TTestSuite.Create('Some trivial tests',
[
TTestCaseFirst.Suite,
TTestCaseSecond.Suite
]);
ATestSuite := TTestSuite.create('Some other trivial tests');
ATestSuite.addTest(TTestCaseFirst.Suite);
ATestSuite.addTest(BTestSuite);
Result := ATestSuite;
end;
我們先使用TtestSuite.Create創建一個一層的TestSuite, BtestSuite.然后在將BtestSuite加入到AtestSuite。
最后將initialization處的代碼改為如下:
initialization
TestFramework.RegisterTest('Simple Test', UnitTests);
end.
注冊AtestSuite就可以了,以下是運行結果:

控制臺(console)模式
如果想在Dos方式下直接運行TestCase,只要修改Dpr文件即可。
{$APPTYPE CONSOLE}
program TestProject;
uses
Forms,
TestFrameWork,
GUITestRunner,
TextTestRunner,
TestUnit in 'TestUnit.pas';
{$R *.res}
begin
Application.Initialize;
// GUITestRunner.RunRegisteredTests;
TextTestRunner.RunRegisteredTests;
end.
先定義應用程序類型,加入{$APPTYPE CONSOLE},然后使用TextTestRunner替代GUITestRunner就可以了。
確省情況下,測試程序將把運行所有的TestCase后給出報告,如果想在達到一定錯誤就停止運行,可以使用
TextTestRunner.RunRegisteredTests(rxbHaltOnFailures);
Dunit附加功能
使用Dunit的附加功能要先在Uses中加入:
TestExtensions, // needed for TrepeatedTest
Dunit的主要附加功能有:
1、重復運行某一TestCase
2、使用TtestSetup類初試化
Dunit的TestExtensions還提到了兩個類TactiveTest、TexceptionTestCase來實現:
3、在獨立線程中運行測試
4、Exception測試
但在Dunit中的最新源碼,這兩個類只是簡單繼承了TtestDecorator而沒有做任何的修改,在Dunit的Readme中也沒有提到這兩個類的用法。因此應該屬于還沒有實現的類。
重復運行TestCase
要重復運行某一TestCase,只需要將initialization里面的注冊代碼
TestFramework.RegisterTest(TTestCaseFirst.Suite);
簡單替換為:
TestFramework.RegisterTest(TRepeatedTest.Create(TTestCaseFirst.Suite, 2));
就可以,TRepeatedTest.Create的第一個參數為要重復的TestSuite/TestCase,第二個參數代表次數。運行后的結果如下:

請注意,TestCaseFirst前面多了“2x”。
使用TtestSetup類
使用TtestSetup類的作用就是在運行所有的測試方法前后只運行一次Setup幾TearDown?梢杂糜趧摻〝祿䦷爝B接等等。
要使用TtestSetup,我們先在《創建一個簡單的例子》一節中創建的TestUnit中聲明一個新的類(先在Uses中加入Dialogs,TestExtensions)
TestSetupTest = class (TTestSetup)
protected
procedure SetUp; override; //初始化類
procedure TearDown; override; //清除數據
end;
加入實現代碼
procedure TestSetupTest.SetUp;
begin
ShowMessage('TestSetupTest Setup');
end;
procedure TestSetupTest.TearDown;
begin
ShowMessage('TestSetupTest TearDown');
end;
修改TtestCaseFirst.SetUp及TTestCaseFirst.TearDown,加入下面加粗語句。
procedure TTestCaseFirst.SetUp;
begin
BeTestForm := TBeTestForm.Create(Nil);
ShowMessage('TTestCaseFirst Setup');
end;
procedure TTestCaseFirst.TearDown;
begin
BeTestForm.Destroy;
ShowMessage('TTestCaseFirst TearDown');
end;
最后將initialization改為
initialization
//TestFramework.RegisterTest(TTestCaseFirst.Suite);
TestFramework.RegisterTest(TestSetupTest.Create(TTestCaseFirst.Suite));
end.
運行之后的結果如下:

注意TtestCaseFirst前面加了”[d]”。運行一次測試就可以清楚看到TestSetupTest類中Setup和TearDown只運行了一次,而TtestCaseFirst中的Setup和TearDown運行了兩次
測試Exception
雖然TexceptionTestCase沒有實現,但是Dunit在源碼附加\examples\testexception目錄中有一個如何測試Exception的例子。
主要的實現在procedure TTestMyObject.CheckException和procedure TTestMyObjectOverrideRunTest.RunTest中。具體的實現可以看代碼。