如何访问 TTestSetup 类中 TTestCase 的字段

发布于 2024-10-16 04:00:37 字数 799 浏览 2 评论 0原文

我正在使用 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));

I am creating unit tests with DUnit. I have a class that takes quite a long time to initialize.

I derive a class TMyTestSetup from TTestSetup and override its Setup method. This SetUp method is only called once for all the tests in my TTestCase. I put the Initialization process in the TMyTestSetup.SetUp routine to increase performance.

My problem is how can I access the object I want to initialize, which is a field of my TMyTest in the TestSetup class? Is the only way to do it declaring it globally?

untested short example:

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));

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

林空鹿饮溪 2024-10-23 04:00:37

诀窍是在 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.

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

The trick is to use a public class variable in the TMyTestSetup class.

Like this (tested and working, complete) example:

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.

As the comments in the sample indicate, I have used a randomised private variable, and some debug trace output, to confirm that each test call with the test suite is to the same copy of the target object, but that we are getting a different copy of the target object each time the test suite is run.

享受孤独 2024-10-23 04:00:37

您可以从 TTestSuite 类派生一个新的测试套件类,并重写其 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;

You can derive a new Test Suite class from TTestSuite class, and override its SetUp and TearDown methods, then you can add your test cases to this particular test suite, and register the suite.

This way, Setup and TearDown methods of your test suite class will be called once, and SetUp and TearDown methods of each test case will be called for every test method defined in that test case.

Execution order will be like this:

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;
停滞 2024-10-23 04:00:37

只有一个已发布的方法,该方法又调用所有其他测试方法,这就是
让Setup 和TearDown 过程仅调用一次的惰性但更快的方法。

Having just one published method, which in turn call all your other test methods is the
lazy but quicker way of having the Setup and TearDown procedure called only once.

毁梦 2024-10-23 04:00:37

您无法初始化整个测试套件的 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”地址不同。这意味着 Test1 和 Test2 调用的 TMyTestCase 对象实例是不同的。

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

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

You can't initialize TTestCase fields for a whole test suite, and here is an explanation why:

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.

If you run the above unit test you will see that the 'Self' addresses shown in Test1 and Test2 methods are different. That means that TMyTestCase object instances are different for Test1 and Test2 calls.

Consequently, any fields you may declare in TMyTestCase class are volatile between test method's calls.

To perform "global" initialization you should declare your object globally, not as TMyTestCase field.

把人绕傻吧 2024-10-23 04:00:37

使用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])
  ));

你可能会觉得有点令人反感,但它确实有效!

Using TTestSetup you could do something like this:

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])
  ));

It feels somewhat revolting mind you, but it does the job!

倒数 2024-10-23 04:00:37

根据您的 Delphi 版本,您只需将 TMyTest.fTakes4Ever2Init 字段设置为 public class var 即可从测试设置中对其进行初始化。 (与单元全局变量相比,这将更具 OOP 风格。)

Depending on your Delphi version, you can simply make the TMyTest.fTakes4Ever2Init field a public class var to initialize it from the test setup. (This would be more OOP style compared to a unit-global variable.)

花开半夏魅人心 2024-10-23 04:00:37

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

这是一个相当老的问题,但我可以想象人们仍然会遇到这个问题。我做到了。

我对这个问题的最初解决方案也使用了类变量或全局变量。但实际上这个解决方案很糟糕,因为它使得重用 TTestSetup 派生类变得非常困难。因此我进行了一些调试以了解 DUnit 内部的工作原理。 (我在我的旗舰应用程序和库中广泛使用 DUnit)

事实证明,您实际上可以从 TTestSetup.RunTest 中访问子测试。在此方法中,您将获得包装/装饰的子测试的句柄,它实际上是一个从我的 TTestCase.Suite 创建的 TTestSuite。因此,我循环遍历 ITestsuite 子测试(实际上是对 TtestCase 中每个已发布方法的方法调用),并检查它们是否支持我的 ITestDecoratable接口,如果是这样我调用SetupDecoration

接下来,通过调用继承的Runtest来执行实际测试。

最后,我们再次执行相同的循环,这次调用 TearDownDecoration

这并没有修复嵌套的 TTestsetup 情况,因此我添加了一个检查 TTestDecorator.Test 是否直接支持 ITestDecoratable ,并相应地执行。就此而言,我还在我的 TDecoratedTestSetup 中实现了 ITestDecoratable,因此也支持嵌套。

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

我可以想象人们宁愿直接在 TTestCaseTTestDecorator 中实现这些方法,但现在我已将其放在一个单独的单元中。我将向相应的 sourceforge 站点添加票证。


这是我的解决方案:

unit uDecoratorTestBase;

interface

uses TestFramework,TestExtensions;

type
/// <summary>
///   when a test implements the interface below, and the TDecoratedTestSetup
///  is used, these methods get called dureing testing.
/// </summary>
  ITestDecoratable=interface (ITest)
    ['{468A66E9-937B-4C45-9321-A1796F93470C}']
    /// <summary>
    ///   gets called before the Setup call
    /// </summary>
    procedure SetupDecoration(const aDecorator:ITestDecorator);
    /// <summary>
    ///   gets called after the teardown call
    /// </summary>
    procedure TeardownDecoration(const aDecorator:ITestDecorator);
  end;

  /// <summary>
  ///  an alternatine to TTestSetup this implementation tries to decorate
  ///  any subtests when it is executed through the ITestDecoratable interface
  ///  bonus feature is that iself also supports the ItestDecoratable interface
  ///  allowing for multiple layes of decoration
  /// </summary>
  TDecoratedTestSetup=class(TTestDecorator,ITestDecoratable)
  private
  protected
    procedure RunTest(ATestResult: TTestResult); override;
    procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
  end;
  /// <summary>
  ///   Same as TTestcase, but adds the ITestDecoratable interface. Override
  ///  the routines below to get values from the decorator class through
  ///  the provided ITestDecorator interface.
  /// </summary>
  TDecoratedTestCase=class(TTestCase,ITestDecoratable)
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
  end;

implementation

uses
  sysutils;

{ TDecoratedTestSetup }

procedure TDecoratedTestSetup.RunTest(ATestResult: TTestResult);
var lDecoratable:ITestDecoratable;
var lSuite:ITestSuite;
begin
  if Supports(Test,ITestDecoratable,lDecoratable) then
  try
    lDecoratable.SetupDecoration(self);
    inherited;
  finally
    lDecoratable.TeardownDecoration(self);
  end
  else if Supports(Test,ITestSuite,lSuite) then
  try
    for var I := 0 to lSuite.Tests.Count-1 do
      if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
        lDecoratable.SetupDecoration(self);
    inherited;
  finally
    for var I := 0 to lSuite.Tests.Count-1 do
      if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
        lDecoratable.TeardownDecoration(self);
  end
  else inherited;
end;

procedure TDecoratedTestSetup.SetupDecoration(const aDecorator: ITestDecorator);
begin
  // override to initialize class fields using the decorator
end;

procedure TDecoratedTestSetup.TeardownDecoration(const aDecorator: ITestDecorator);
begin
  // override to finalize class fields previously initialized through SetupDecoration
end;

{ TDecoratedTestCase }

procedure TDecoratedTestCase.SetupDecoration(const aDecorator: ITestDecorator);
begin
  // override to initialize class fields using the decorator
end;

procedure TDecoratedTestCase.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  // override to finalize class fields previously initialized through SetupDecoration
end;

end.

单元测试

这是我为解决方案创建的单元测试。运行这个应该会带来一些启发,并希望让您了解发生了什么。

unit UnitTestDecorator;

interface

uses
  TestFrameWork,uDecoratorTestBase;

type
  /// <summary>
  ///   Perofms the actuel self-test by running decorated testcases
  /// </summary>
  TTestDecoratorTest=class(TTestCase)
  private
  protected
    procedure SetUp; override;
  published
    procedure TestDecorated;
  end;



implementation

type
  TMyDecoratedTestCase=class(TDecoratedTestCase)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure CheckSetupTearDown;
    procedure FailTest;
  end;

  TMyInnerDecoratedTestSetup=class(TDecoratedTestSetup)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure CheckSetupTearDown;
  end;

  TMyOuterDecoratedTestSetup=class(TDecoratedTestSetup)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
  published
    procedure CheckSetupTearDown;
  end;


{ TTestDecoratorTest }

procedure TTestDecoratorTest.Setup;
begin
  inherited;
  TMyDecoratedTestCase.FDecorateCalls:=0;
  TMyDecoratedTestCase.FUndecorateCalls:=0;
  TMyInnerDecoratedTestSetup.FDecorateCalls:=0;
  TMyInnerDecoratedTestSetup.FUndecorateCalls:=0;
  TMyOuterDecoratedTestSetup.FDecorateCalls:=0;
  TMyOuterDecoratedTestSetup.FUndecorateCalls:=0;
end;

procedure TTestDecoratorTest.TestDecorated;
begin
  var lTestCaseSuite:=TMyDecoratedTestCase.Suite;
  var lInnerTestSetup:=TMyInnerDecoratedTestSetup.Create(lTestCaseSuite) as ITest;
  var lOuterTestSetup:=TMyOuterDecoratedTestSetup.Create(lInnerTestSetup) as ITest;
  var lTestResult:=TTestResult.Create;
  try
    lOuterTestSetup.RunTest(lTestResult);
    CheckEquals(0,lTestResult.ErrorCOunt,'lTestResult.ErrorCOunt');
    CheckEquals(1,lTestResult.FailureCOunt,'lTestResult.FailureCOunt');
  finally
    lTestResult.Free;
  end;

  CheckEquals(2,TMyDecoratedTestCase.FDecorateCalls,'TMyDecoratedTestCase.FDecorateCalls');
  CheckEquals(TMyDecoratedTestCase.FDecorateCalls,TMyDecoratedTestCase.FUndecorateCalls,'TMyDecoratedTestCase.FUndecorateCalls');

  CheckEquals(1,TMyInnerDecoratedTestSetup.FDecorateCalls,'TMyInnerDecoratedTestSetup.FDecorateCalls');
  CheckEquals(TMyInnerDecoratedTestSetup.FDecorateCalls,TMyInnerDecoratedTestSetup.FUndecorateCalls,'TMyInnerDecoratedTestSetup.FUndecorateCalls');

  CheckEquals(0,TMyOuterDecoratedTestSetup.FDecorateCalls,'TMyOuterDecoratedTestSetup.FDecorateCalls');
  CheckEquals(TMyOuterDecoratedTestSetup.FDecorateCalls,TMyOuterDecoratedTestSetup.FUndecorateCalls,'TMyOuterDecoratedTestSetup.FUndecorateCalls');
end;

{ TMyDecoratedTestCase }

procedure TMyDecoratedTestCase.CheckSetupTearDown;
begin
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;

procedure TMyDecoratedTestCase.FailTest;
begin
  Fail('Intentionally');
end;

procedure TMyDecoratedTestCase.Setup;
begin
  inherited;
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;

procedure TMyDecoratedTestCase.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FDecorateCalls);
end;

procedure TMyDecoratedTestCase.TearDown;
begin
  inherited;
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;

procedure TMyDecoratedTestCase.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

{ TMyInnerDecoratedTestSetup }

procedure TMyInnerDecoratedTestSetup.CheckSetupTearDown;
begin
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;

procedure TMyInnerDecoratedTestSetup.Setup;
begin
  inherited;
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;

procedure TMyInnerDecoratedTestSetup.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inc(FDecorateCalls);
  inherited;
end;

procedure TMyInnerDecoratedTestSetup.TearDown;
begin
  inherited;
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;

procedure TMyInnerDecoratedTestSetup.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

{ TMyOuterDecoratedTestSetup }

procedure TMyOuterDecoratedTestSetup.CheckSetupTearDown;
begin
  CheckEquals(0,FDecorateCalls);
  CheckEquals(0,FUnDecorateCalls);
end;

procedure TMyOuterDecoratedTestSetup.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FDecorateCalls);
end;

procedure TMyOuterDecoratedTestSetup.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

initialization
  RegisterTests('Decorator Test setup extensions for DUnit',
                     [
                       TTestDecoratorTest.Suite
                     ]);

end.

The better solution (... IMHO)

It's a pretty old question, but I can imagine people still bumping into this. I did.

My initial solution to this problem also used class vars or globals. But indeed this solution is bad as it makes it very hard to re-use TTestSetup derived classes. Hence I debugged a bit to find how DUnit works internally. (I use DUnit extensively on my flagship app and libs)

As it turns out you actually can get access to the subtests: from within TTestSetup.RunTest. In this method you get a handle to the wrapped/decorated Subtest, which actually turned out to be a TTestSuite, created from my TTestCase.Suite. So I loop through the ITestsuite subtests (which are actually method calls for each published method in your TtestCase), and check if they support my ITestDecoratable interface, if so I call the SetupDecoration.

Next, the actual test is performed by calling the inherited Runtest.

And finally we go through the same loop again, this time calling TearDownDecoration.

This did not fix the nested TTestsetup case, so I added a check if TTestDecorator.Test supports ITestDecoratable directly, and execute accordingly. For that matter, I alsom implemented the ITestDecoratable in my TDecoratedTestSetup so nesting is also supported.

And came up with this solution. I even created a unit test for it, and everything works as intended.

I can imagine one would rather implement these methods in TTestCase and TTestDecorator directly, but for now I have put it in a separate unit. I'll add a ticket to the corresponding sourceforge site.


Here's my solution:

unit uDecoratorTestBase;

interface

uses TestFramework,TestExtensions;

type
/// <summary>
///   when a test implements the interface below, and the TDecoratedTestSetup
///  is used, these methods get called dureing testing.
/// </summary>
  ITestDecoratable=interface (ITest)
    ['{468A66E9-937B-4C45-9321-A1796F93470C}']
    /// <summary>
    ///   gets called before the Setup call
    /// </summary>
    procedure SetupDecoration(const aDecorator:ITestDecorator);
    /// <summary>
    ///   gets called after the teardown call
    /// </summary>
    procedure TeardownDecoration(const aDecorator:ITestDecorator);
  end;

  /// <summary>
  ///  an alternatine to TTestSetup this implementation tries to decorate
  ///  any subtests when it is executed through the ITestDecoratable interface
  ///  bonus feature is that iself also supports the ItestDecoratable interface
  ///  allowing for multiple layes of decoration
  /// </summary>
  TDecoratedTestSetup=class(TTestDecorator,ITestDecoratable)
  private
  protected
    procedure RunTest(ATestResult: TTestResult); override;
    procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
  end;
  /// <summary>
  ///   Same as TTestcase, but adds the ITestDecoratable interface. Override
  ///  the routines below to get values from the decorator class through
  ///  the provided ITestDecorator interface.
  /// </summary>
  TDecoratedTestCase=class(TTestCase,ITestDecoratable)
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
  end;

implementation

uses
  sysutils;

{ TDecoratedTestSetup }

procedure TDecoratedTestSetup.RunTest(ATestResult: TTestResult);
var lDecoratable:ITestDecoratable;
var lSuite:ITestSuite;
begin
  if Supports(Test,ITestDecoratable,lDecoratable) then
  try
    lDecoratable.SetupDecoration(self);
    inherited;
  finally
    lDecoratable.TeardownDecoration(self);
  end
  else if Supports(Test,ITestSuite,lSuite) then
  try
    for var I := 0 to lSuite.Tests.Count-1 do
      if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
        lDecoratable.SetupDecoration(self);
    inherited;
  finally
    for var I := 0 to lSuite.Tests.Count-1 do
      if Supports(lSuite.Tests[i],ITestDecoratable,lDecoratable) then
        lDecoratable.TeardownDecoration(self);
  end
  else inherited;
end;

procedure TDecoratedTestSetup.SetupDecoration(const aDecorator: ITestDecorator);
begin
  // override to initialize class fields using the decorator
end;

procedure TDecoratedTestSetup.TeardownDecoration(const aDecorator: ITestDecorator);
begin
  // override to finalize class fields previously initialized through SetupDecoration
end;

{ TDecoratedTestCase }

procedure TDecoratedTestCase.SetupDecoration(const aDecorator: ITestDecorator);
begin
  // override to initialize class fields using the decorator
end;

procedure TDecoratedTestCase.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  // override to finalize class fields previously initialized through SetupDecoration
end;

end.

Unit Test

And here's the unit test I created for my solution. Running this should shed some light and hopefully make you understand what's going on.

unit UnitTestDecorator;

interface

uses
  TestFrameWork,uDecoratorTestBase;

type
  /// <summary>
  ///   Perofms the actuel self-test by running decorated testcases
  /// </summary>
  TTestDecoratorTest=class(TTestCase)
  private
  protected
    procedure SetUp; override;
  published
    procedure TestDecorated;
  end;



implementation

type
  TMyDecoratedTestCase=class(TDecoratedTestCase)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure CheckSetupTearDown;
    procedure FailTest;
  end;

  TMyInnerDecoratedTestSetup=class(TDecoratedTestSetup)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure CheckSetupTearDown;
  end;

  TMyOuterDecoratedTestSetup=class(TDecoratedTestSetup)
  private
    class var FDecorateCalls:integer;
    class var FUndecorateCalls:integer;
  protected
    procedure SetupDecoration(const aDecorator:ITestDecorator); override;
    procedure TeardownDecoration(const aDecorator:ITestDecorator); override;
  published
    procedure CheckSetupTearDown;
  end;


{ TTestDecoratorTest }

procedure TTestDecoratorTest.Setup;
begin
  inherited;
  TMyDecoratedTestCase.FDecorateCalls:=0;
  TMyDecoratedTestCase.FUndecorateCalls:=0;
  TMyInnerDecoratedTestSetup.FDecorateCalls:=0;
  TMyInnerDecoratedTestSetup.FUndecorateCalls:=0;
  TMyOuterDecoratedTestSetup.FDecorateCalls:=0;
  TMyOuterDecoratedTestSetup.FUndecorateCalls:=0;
end;

procedure TTestDecoratorTest.TestDecorated;
begin
  var lTestCaseSuite:=TMyDecoratedTestCase.Suite;
  var lInnerTestSetup:=TMyInnerDecoratedTestSetup.Create(lTestCaseSuite) as ITest;
  var lOuterTestSetup:=TMyOuterDecoratedTestSetup.Create(lInnerTestSetup) as ITest;
  var lTestResult:=TTestResult.Create;
  try
    lOuterTestSetup.RunTest(lTestResult);
    CheckEquals(0,lTestResult.ErrorCOunt,'lTestResult.ErrorCOunt');
    CheckEquals(1,lTestResult.FailureCOunt,'lTestResult.FailureCOunt');
  finally
    lTestResult.Free;
  end;

  CheckEquals(2,TMyDecoratedTestCase.FDecorateCalls,'TMyDecoratedTestCase.FDecorateCalls');
  CheckEquals(TMyDecoratedTestCase.FDecorateCalls,TMyDecoratedTestCase.FUndecorateCalls,'TMyDecoratedTestCase.FUndecorateCalls');

  CheckEquals(1,TMyInnerDecoratedTestSetup.FDecorateCalls,'TMyInnerDecoratedTestSetup.FDecorateCalls');
  CheckEquals(TMyInnerDecoratedTestSetup.FDecorateCalls,TMyInnerDecoratedTestSetup.FUndecorateCalls,'TMyInnerDecoratedTestSetup.FUndecorateCalls');

  CheckEquals(0,TMyOuterDecoratedTestSetup.FDecorateCalls,'TMyOuterDecoratedTestSetup.FDecorateCalls');
  CheckEquals(TMyOuterDecoratedTestSetup.FDecorateCalls,TMyOuterDecoratedTestSetup.FUndecorateCalls,'TMyOuterDecoratedTestSetup.FUndecorateCalls');
end;

{ TMyDecoratedTestCase }

procedure TMyDecoratedTestCase.CheckSetupTearDown;
begin
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;

procedure TMyDecoratedTestCase.FailTest;
begin
  Fail('Intentionally');
end;

procedure TMyDecoratedTestCase.Setup;
begin
  inherited;
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;

procedure TMyDecoratedTestCase.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FDecorateCalls);
end;

procedure TMyDecoratedTestCase.TearDown;
begin
  inherited;
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;

procedure TMyDecoratedTestCase.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

{ TMyInnerDecoratedTestSetup }

procedure TMyInnerDecoratedTestSetup.CheckSetupTearDown;
begin
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls');
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls');
end;

procedure TMyInnerDecoratedTestSetup.Setup;
begin
  inherited;
  CheckNotEquals(0,FDecorateCalls,'FDecorateCalls'); // decorate must take place BEFORE setup
end;

procedure TMyInnerDecoratedTestSetup.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inc(FDecorateCalls);
  inherited;
end;

procedure TMyInnerDecoratedTestSetup.TearDown;
begin
  inherited;
  CheckEquals(0,FUnDecorateCalls,'FUnDecorateCalls'); // undecorate must take place AFTER Teardown
end;

procedure TMyInnerDecoratedTestSetup.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

{ TMyOuterDecoratedTestSetup }

procedure TMyOuterDecoratedTestSetup.CheckSetupTearDown;
begin
  CheckEquals(0,FDecorateCalls);
  CheckEquals(0,FUnDecorateCalls);
end;

procedure TMyOuterDecoratedTestSetup.SetupDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FDecorateCalls);
end;

procedure TMyOuterDecoratedTestSetup.TeardownDecoration(
  const aDecorator: ITestDecorator);
begin
  inherited;
  inc(FUnDecorateCalls);
end;

initialization
  RegisterTests('Decorator Test setup extensions for DUnit',
                     [
                       TTestDecoratorTest.Suite
                     ]);

end.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文