How to access fields of a TTestCase in a TTestSetup class

后端 未结 7 1252
死守一世寂寞
死守一世寂寞 2021-02-07 13:37

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 met

7条回答
  •  暖寄归人
    2021-02-07 14:31

    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
    /// 
    ///   when a test implements the interface below, and the TDecoratedTestSetup
    ///  is used, these methods get called dureing testing.
    /// 
      ITestDecoratable=interface (ITest)
        ['{468A66E9-937B-4C45-9321-A1796F93470C}']
        /// 
        ///   gets called before the Setup call
        /// 
        procedure SetupDecoration(const aDecorator:ITestDecorator);
        /// 
        ///   gets called after the teardown call
        /// 
        procedure TeardownDecoration(const aDecorator:ITestDecorator);
      end;
    
      /// 
      ///  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
      /// 
      TDecoratedTestSetup=class(TTestDecorator,ITestDecoratable)
      private
      protected
        procedure RunTest(ATestResult: TTestResult); override;
        procedure SetupDecoration(const aDecorator:ITestDecorator); virtual;
        procedure TeardownDecoration(const aDecorator:ITestDecorator); virtual;
      end;
      /// 
      ///   Same as TTestcase, but adds the ITestDecoratable interface. Override
      ///  the routines below to get values from the decorator class through
      ///  the provided ITestDecorator interface.
      /// 
      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
      /// 
      ///   Perofms the actuel self-test by running decorated testcases
      /// 
      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.
    

提交回复
热议问题