首页  编辑  

Delphi单元测试工具Dunit介绍

Tags: /超级猛料/IDE.集成开发环境/编译、调试/   Date Created:

Delphi单元测试工具Dunit介绍

广州邦讯科技 测试部 谢慧强

xiehuiqiang@21cn.com

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 <D:dunit-5.0.11docAPIIDH_Unit_TestFramework.htm> The framework itself.
TestExtensions.pas <D:dunit-5.0.11docAPIIDH_Unit_TestExtensions.htm> Decorator classes that may be used to extend test cases.
GUITesting.pas <D:dunit-5.0.11docAPIIDH_Unit_GUITesting.htm> Classes for testing user interfaces (Forms and Dialogs).
TextTestRunner.pas <D:dunit-5.0.11docAPIIDH_Unit_TextTestRunner.htm> Routines to run tests in console mode.
GUITestRunner.pas <D:dunit-5.0.11docAPIIDH_Unit_GUITestRunner.htm> The graphical user interface to the framework..
GUITestRunner.dfm <D:dunit-5.0.11docAPIIDH_Unit_GUITestRunner.htm> 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 中。具体的实现可以看代码。

5396-dunit-7.2.0[1].zip (943.1KB)
img_12093.bmp (459.3KB)
img_13550.bmp (597.5KB)
img_19672.bmp (323.6KB)
img_27979.bmp (631.7KB)
img_29851.bmp (490.5KB)
img_4381.bmp (403.5KB)
img_5823.bmp (455.0KB)
img_8053.bmp (328.8KB)
img_8833.bmp (130.3KB)
img_9009.bmp (130.3KB)