How to access fields of a TTestCase in a TTestSetup class

后端 未结 7 1258
死守一世寂寞
死守一世寂寞 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:07

    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.

    0 讨论(0)
  • 2021-02-07 14:09

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

    0 讨论(0)
  • 2021-02-07 14:12

    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!

    0 讨论(0)
  • 2021-02-07 14:13

    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.

    0 讨论(0)
  • 2021-02-07 14:27

    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;
    
    0 讨论(0)
  • 2021-02-07 14:29

    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.

    0 讨论(0)
提交回复
热议问题