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。可以用于创建数据库连接等等。
要使用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中。具体的实现可以看代码。