如何在TTestSetup类中访问TTestCase的字段

时间:2011-02-05 13:00:07

标签: delphi dunit unit-testing

我正在使用DUnit创建单元测试。我有一个类需要很长时间才能初始化。

我从TTestSetup派生出一个类TMyTestSetup并覆盖其Setup方法。对于我的TTestCase中的所有测试,只调用一次此SetUp方法。我将初始化过程放在TMyTestSetup.SetUp例程中以提高性能。

我的问题是如何访问我想要初始化的对象,这是TestSetup类中我的TMyTest的一个字段?唯一的方法是在全球范围内宣布它吗?

未经测试的简短例子:

TMyTestSetup = class(TTestSetup)
  protected
    procedure SetUp; override;
end;

TMyTest = class(TTestcase)
public
    fTakes4Ever2Init : TInits4Ever2Init;
published
  procedure Test1;     
end;

implementation

procedure TMyTestSetup.Setup;
begin
   // How can I access fTakes4Ever2Init from here?
  fTakes4Ever2Init.create // This is the call that takes long
end;

procedure TMyTest.Test1;
begin
  fTakes4Ever2Init.DoSomething;
end;

initialization
  RegisterTest(TMyTestSetup.Create(TMyTest.Suite));

7 个答案:

答案 0 :(得分:9)

诀窍是在TMyTestSetup类中使用公共类变量。

像这样(测试和工作,完整)示例:

unit TestTestUnit;

interface

uses
  TestFramework, TestExtensions;

type
  TInits4Ever2Init = class
  private
    FValue: integer;
  public
    constructor Create;
    procedure   DoSomething1;
    procedure   DoSomething2;
    procedure   DoSomething3;
  end;

type
  TMyTestSetup = class(TTestSetup)
  public class var
    fTakes4Ever2Init: TInits4Ever2Init;
  protected
    procedure SetUp; override;
  end;

  TMyTest = class(TTestCase)
  published
    procedure Test1;
    procedure Test2;
    procedure Test3;
  end;

implementation

uses
  SysUtils, Windows;

{ TMyTestSetup }

procedure TMyTestSetup.Setup;
begin
  fTakes4Ever2Init := TInits4Ever2Init.create; // This is the call that takes long
end;

{ TMyTest }

procedure TMyTest.Test1;
begin
  TMyTestSetup.fTakes4Ever2Init.DoSomething1;
end;

procedure TMyTest.Test2;
begin
  TMyTestSetup.fTakes4Ever2Init.DoSomething2;
end;

procedure TMyTest.Test3;
begin
  TMyTestSetup.fTakes4Ever2Init.DoSomething3;
end;

{ TInits4Ever2Init }

constructor TInits4Ever2Init.Create;
begin
  inherited Create;

  // FValue and Format('%p, %d', [Pointer(Self), FValue])) are to confirm
  //   that we are talking to the same object for all the tests,
  //   but that the object is different each time we run the test suite.

  Randomize;
  FValue := Random(10000);

  OutputDebugString(pAnsiChar('-- TInits4Ever2Init.Create: '
    + Format('%p, %d', [Pointer(Self), FValue])));
end;

procedure TInits4Ever2Init.DoSomething1;
begin
  OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething1: '
    + Format('%p, %d', [Pointer(Self), FValue])));
end;

procedure TInits4Ever2Init.DoSomething2;
begin
  OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething2: '
    + Format('%p, %d', [Pointer(Self), FValue])));
end;

procedure TInits4Ever2Init.DoSomething3;
begin
  OutputDebugString(pAnsiChar('-- TInits4Ever2Init.DoSomething3: '
    + Format('%p, %d', [Pointer(Self), FValue])));
end;

initialization
  RegisterTest(TMyTestSetup.Create(TMyTest.Suite));
end.

正如示例中的注释所示,我使用了一个随机私有变量和一些调试跟踪输出,以确认每个测试套件的测试调用是与目标对象的同一个副本,但我们得到了每次运行测试套件时,目标对象的不同副本。

答案 1 :(得分:4)

您可以从TTestSuite类派生一个新的Test Suite类,并覆盖其SetUp和TearDown方法,然后您可以将测试用例添加到此特定测试套件中,并注册该套件。

这样,您的测试套件类的Setup和TearDown方法将被调用一次,并且将针对该测试用例中定义的每个测试方法调用每个测试用例的SetUp和TearDown方法。

执行顺序如下:

TestSuite.SetUp;

-- TestCase1.Setup;
---- TestCase1.Test1;
-- TestCase1.TearDown;
-- TestCase1.Setup;
---- TestCase1.Test2;
-- TestCase1.TearDown;

-- TestCase2.Setup;
---- TestCase2.Test1;
-- TestCase2.TearDown;
-- TestCase2.Setup;
---- TestCase2.Test2;
-- TestCase2.TearDown;

-- TestCaseN.Setup;
---- TestCaseN.Test1;
-- TestCaseN.TearDown;
-- TestCaseN.Setup;
---- TestCaseN.Test2;
-- TestCaseN.TearDown;

TestSuite.TearDown;

答案 2 :(得分:3)

只有一个已发布的方法,而后者又会调用所有其他测试方法 只调用一次Setup和TearDown过程的懒惰但更快的方法。

答案 3 :(得分:2)

您无法初始化整个测试套件的TTestCase字段,以下是解释原因:

unit Tests3;

interface

uses
  TestFramework, TestExtensions, Windows, Forms, Dialogs, Controls, Classes,
  SysUtils, Variants, Graphics, Messages;

type
  TMyTestCase = class(TTestCase)
  private
    FValue: Integer;
  published
    procedure Test1;
    procedure Test2;
  end;

implementation

{ TMyTestCase }

procedure TMyTestCase.Test1;
begin
  FValue:= 99;
  ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;

procedure TMyTestCase.Test2;
begin
  ShowMessage(Format('%p, %d', [Pointer(Self), FValue]));
end;

initialization
  RegisterTest(TMyTestCase.Suite);
end.

如果运行上述单元测试,您将看到Test1和Test2方法中显示的“Self”地址不同。这意味着TMyTestCase对象实例对于Test1和Test2调用是不同的。

因此,您在TMyTestCase类中声明的任何字段在测试方法的调用之间都是不稳定的。

要执行“全局”初始化,您应该全局声明对象,而不是TMyTestCase字段。

答案 4 :(得分:1)

使用TTestSetup你可以这样做:

type
  TMyTestSetup = class(TTestSetup)
  private
    FValue: Integer;
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  end;

  TMyTestCase = class(TTestCase)
  published
    procedure TestSomething;
  end;

var
  TestSetup: TMyTestSetup;

procedure TMyTestSetup.SetUp;
begin
  inherited;
  TestSetup := Self;
  FValue := 42;
end;

procedure TMyTestSetup.TearDown;
begin
  TestSetup := nil;
  inherited;
end;

procedure TMyTestCase.TestSomething;
begin
  CheckEquals(TestSetup.FValue, 42);
end;

initialization
  TestFramework.RegisterTest(TMyTestSetup.Create(
    TTestSuite.Create('My test suite', [TMyTestCase.Suite])
  ));

让你感到有点反感,但是它确实起作用了!

答案 5 :(得分:1)

根据您的Delphi版本,您只需将TMyTest.fTakes4Ever2Init字段设为public class var即可从测试设置初始化它。 (与单位全局变量相比,这将是更多的OOP样式。)

答案 6 :(得分:0)

更好的解决方案(... 恕我直言

这是一个很老的问题,但是我可以想象人们仍然在碰碰这个问题。我做到了。

我对这个问题的最初解决方案还使用了vars或globals类。但是,实际上,这种解决方案是不好的,因为它很难重用git reset <file> git add <file> 派生类。因此,我进行了一些调试以查找DUnit在内部如何工作。 (我在旗舰应用程序和库中广泛使用了DUnit)

事实证明,您实际上可以git status -s | grep "^DU" git status -s | grep "^UD" # or using powershell built-ins : git status -s | sls "^DU" git status -s | sls "^UD" 内部访问子测试。在此方法中,您将获得包裹/装饰的子测试的句柄,该子测试实际上是由我的TTestSetup创建的TTestSetup.RunTest。因此,我遍历了TTestSuite子测试(实际上是TTestCase.Suite中每个已发布方法的方法调用),并检查它们是否支持我的ITestsuite接口,如果是,我称为{{ 1}}。

接下来,通过调用TtestCase进行实际测试。

最后我们再次经历相同的循环,这次调用ITestDecoratable

这不能解决嵌套SetupDecoration的情况,因此我添加了一个检查inherited Runtest是否直接支持TearDownDecoration并相应地执行。为此,我还在TTestsetup中实现了TTestDecorator.Test,因此也支持嵌套。

并提出了此解决方案。我什至为此创建了一个单元测试,并且一切都按预期进行。

我可以想象有人愿意直接在ITestDecoratableITestDecoratable中实现这些方法,但是现在我将其放在一个单独的单元中。我将票证添加到相应的sourceforge网站。


这是我的解决方法:

TDecoratedTestSetup

单元测试

这是我为解决方案创建的单元测试。运行此命令应该会有所启发,并希望使您了解发生了什么。

TTestCase