14

DUnitを使用して単体テストを作成しています。初期化にかなり時間がかかるクラスがあります。

TTestSetupからクラスTMyTestSetupを派生させ、そのSetupメソッドをオーバーライドします。このSetUpメソッドは、TTestCaseのすべてのテストに対して1回だけ呼び出されます。パフォーマンスを向上させるために、初期化プロセスを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));
4

7 に答える 7

8

秘訣は、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.

サンプルのコメントが示すように、ランダム化されたプライベート変数といくつかのデバッグトレース出力を使用して、テストスイートでの各テスト呼び出しがターゲットオブジェクトの同じコピーに対するものであることを確認しましたが、異なるコピーを取得していますテストスイートが実行されるたびに、ターゲットオブジェクトの

于 2012-03-24T02:55:29.353 に答える
5

TTestSuiteクラスから新しいTestSuiteクラスを派生させ、その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;
于 2011-02-05T14:21:46.167 に答える
3

公開されたメソッドを1つだけ持つことで、他のすべてのテストメソッドを呼び出すことは、セットアップとティアダウンのプロシージャを1回だけ呼び出すという、怠惰ですがより迅速な方法です。

于 2012-10-22T20:35:53.760 に答える
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フィールドとしてではなく、オブジェクトをグローバルに宣言する必要があります。

于 2011-02-05T19:21:16.060 に答える
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])
  ));

それはあなたにいくらか反抗的な気がしますが、それは仕事をします!

于 2011-02-05T14:26:02.853 に答える
1

Delphiのバージョンによっては、TMyTest.fTakes4Ever2Initフィールドをapublic class varにして、テストセットアップから初期化することができます。(これは、ユニットグローバル変数と比較してより多くのOOPスタイルになります。)

于 2011-02-06T11:19:31.560 に答える
0

より良い解決策(。。。IMHO

かなり古い質問ですが、人々はまだこれにぶつかっていると想像できます。やった。

この問題に対する私の最初の解決策は、クラス変数またはグローバルも使用しました。TTestSetupしかし、実際、このソリューションは、派生クラスの再利用を非常に困難にするため、良くありません。したがって、DUnitが内部でどのように機能するかを見つけるために少しデバッグしました。(私はフラッグシップアプリとライブラリでDUnitを広範囲に使用しています)

結局のところ、実際にはサブテストにアクセスできTTestSetup.RunTestます:内から。TTestSuiteこのメソッドでは、ラップ/装飾されたサブテストへのハンドルを取得します。これは、実際には、私のから作成された、であることが判明しましたTTestCase.SuiteITestsuiteそこで、サブテスト(実際には、で公開されている各メソッドのメソッド呼び出し)をループし、それらが私のインターフェイスTtestCaseをサポートしているかどうかを確認します。サポートしている場合は、を呼び出します。ITestDecoratableSetupDecoration

次に、を呼び出して実際のテストを実行しinherited Runtestます。

そして最後に、今度はを呼び出して、同じループを再度実行しTearDownDecorationます。

これはネストされたケースを修正しなかったので、直接サポートするTTestsetupかどうかのチェックを追加し、それに応じて実行します。さらに言えば、私も実装されているので、ネスティングもサポートされています。TTestDecorator.TestITestDecoratableITestDecoratableTDecoratedTestSetup

そして、この解決策を思いついた。単体テストも作成しましたが、すべてが意図したとおりに機能します。

TTestCaseこれらのメソッドを直接実装したいと思うかもしれませTTestDecoratorんが、今のところ、別のユニットに配置しています。対応する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.
于 2020-09-19T20:19:33.707 に答える