Delphi customised constructor in TComponent never runs - delphi

i am new to delphi and i am creating a component in delphi 6. but i can't get the constructor to run:
unit MyComms1;
...
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
end;
implementation
constructor TMyComms.MyConstructor;
begin
inherited;
ShowMessage('got here');
end;
it doesn't matter what the constructor is called, but this code doesn't run the constructor at all.
edit
by request, here is how the TMyComms class is initialized (this code is in a different file called TestComms.pas):
unit TestComms;
interface
uses MyComms1, ...
type
TForm1 = class(TForm)
MyCommsHandle = TMyComms;
...
procedure BtnClick(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
procedure TForm1.BtnClick(Sender: TObject);
begin
MyCommsHandle.AnotherMyCommsProcedure;
end;
edit 2
reading some of the answers it looks like constructors must be manually called in delphi. is this correct? if so then this is certainly my main error - i am used to php where the __construct function is automatically called whenever a class is assigned to a handle.

Most likely you are not calling TMyComms.MyConstructor to test your unusual called and used constructor. The way marked with // ** would be th most usual.
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
// the usual override;
// constructor Create(Owner:TComponent);override; // **
constructor Create(AOwner:TComponent);overload; override;
constructor Create(AOwner:TComponent;AnOtherParameter:Integer);overload;
end;
constructor TMyComms.Create(AOwner: TComponent);
begin
inherited ;
ShowMessage('got here Create');
end;
constructor TMyComms.Create(AOwner: TComponent; AnOtherParameter: Integer);
begin
inherited Create(AOwner);
ShowMessage(Format('got here Create with new parametere %d',[AnOtherParameter]));
end;
constructor TMyComms.MyConstructor;
begin
inherited Create(nil);
ShowMessage('got here MyConstructor');
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TMyComms.MyConstructor.Free;
TMyComms.Create(self).Free;
TMyComms.Create(self,1234).Free;
end;

Your code does not follow the Delphi naming guidelines - the constructor should be named Create.
Since you didn't posted the code actually calling the ctor, I guess, that you may not have called it at all. Try to add a button to your form, doubleclick it and add the following code:
procedure TForm1.Button1Click(Sender : TObject)
var comms : TMyComms;
begin
comms := TMyComms.MyConstructor;
comms.Free;
end;
By the way, if you derive from TComponent, you should override constructor with a parameter - otherwise inherited methods may not work properly.
interface
type TMyComms = class(TComponent)
private
protected
public
constructor Create(AOwner : TComponent); override;
end;
implementation
constructor TMyComms.Create(AOwner : TComponent)
begin
inherited Create(AOwner);
// Your stuff
end;
// Somewhere in code
var comms : TMyComms;
begin
comms := TMyComms.Create(nil);
end;

Your custom constructor is not called because you did not call it.
MyComm := TMyComms.MyConstructor;
But you also have an error in your code. Because there is no derived constructor you can inherite with simple inherited.
type
TMyComms = class(TComponent)
public
constructor MyConstructor;
end;
implementation
constructor TMyComms.MyConstructor;
begin
inherited Create( nil ); // !
ShowMessage('got here');
end;
You can use the simple inherited if your custom constructor use the same name and parameters from an existing constructor.
type
TMyComms = class(TComponent)
public
constructor Create( AOwner : TComponent ); override;
end;
implementation
constructor TMyComms.Create( AOwner : TComponent );
begin
inherited; // <- everything is fine
ShowMessage('got here');
end;

Related

How can I initialize my custom IdIOHandler fields?

This is blowing my mind... I just want to make a new IdIOHandler, and, as usual, I need to do some initialization in the constructor... Normally, I override the constructor of the base class, but this TIdIOHandlerStack which I inherit from, is far from "normal" ! It has no constructor to override, and is not known (to me) how it is created.
TIdEnhancedIOHandler = class(TIdIOHandlerStack)
private
FSendBuffer: TIdBytes;
FSendBuff: TDataStream;
public
constructor Create; // <-- I tested all the variations here, but non of them work
destructor Destroy; override;
end;
implementation
constructor TIdEnhancedIOHandler.Create;
begin
inherited Create;
FSendBuff:= TDataStream.Create(#SendBuffer);
end;
destructor TIdEnhancedIOHandler.Destroy;
begin
FSendBuff.Free;
inherited;
end;
initialization
TIdEnhancedIOHandler.SetDefaultClass;
Where should I put my intitialization code so that it is executed when a new instance of TIdEnhancedIOHandler is created BY DEFAULT in all Indy Components which use IOHandlers ?
I found it... It was the InitComponent method that I must override.
TIdEnhancedIOHandler = class(TIdIOHandlerStack)
private
FSendBuffer: TIdBytes;
FSendBuff: TDataStream;
public
procedure InitComponent; override;
destructor Destroy; override;
end;
implementation
procedure TIdEnhancedIOHandler.InitComponent;
begin
inherited;
FSendBuff:= TDataStream.Create(#SendBuffer);
end;

Delphi: How to overload the parent constructor in a descendant but hide it on other different descendant

I read this answer about constructors and its directives (reintroduce, overload, virtual, override, etc.) but I can't reach the goal I want. Check the following pseudo-code (I mean the code without any directive yet):
TBaseClass = class
constructor Create;
end;
TStringStuff = class (TBaseClass)
constructor Create(str: string);
end;
TNumberStuff = class (TBaseClass)
constructor Create(num: integer);
constructor Create(num: decimal);
end;
I want a TStringStuff object can be created using its own constructor and the parent one:
var
StrStuff: TStringStuff;
begin
StrStuff:=TStringStuff.Create();
//or
StrStuff:=TStringStuff.Create('bla');
end;
but I also want a TNumberStuff object can be created using ONLY its own constructors, i.e. if someone use my library he wont be able to create a TNumberStuff without parameter:
var
NumStuff: TNumberStuff ;
begin
NumStuff:=TNumberStuff.Create(10);
//or
NumStuff:=TNumberStuff.Create(10.5);
// but NOT: NumStuff:=TNumberStuff.Create();
end;
So how to use the directives to achieve my goals?
(I am using Delphi 10.2 Tokyo)
For a variety of reasons you can't achieve exactly what you want, but this is as close as you can get.
I have introduced a 'superbase' class if you like with a hidden constructor, only visible in that unit. The only function of TBaseClass now is to 'expose' the constructor so that you can create instances of TBaseClass.
unit Test1;
interface
type
TBaseBaseClass = class
// This does all the work of TBaseClass, but hides the contructor
private
constructor Create; reintroduce; // this can only be accessed within this unit
end;
TBaseClass = class(TBaseBaseClass)
// a creatable class. No actual work is done here. It's only purpose is to
// 'expose' the base constructor
public
constructor Create; reintroduce;
end;
TStringStuff = class( TBaseBaseClass )
public
constructor Create; reintroduce; overload;
constructor Create( str : string ); reintroduce; overload;
end;
TNumStuff = class( TBaseBaseClass )
public
constructor Create( num : integer ); reintroduce; overload;
constructor Create( num : single ); reintroduce; overload;
end;
implementation
{ TStringStuff }
constructor TStringStuff.Create(str: string);
begin
inherited Create;
// ...
// other stuff
end;
constructor TStringStuff.Create;
begin
inherited Create;
// does no extra work! 'exposes' TBaseBaseClass constructor
// but required because of rules of polymorphism
end;
{ TBaseBaseClass }
constructor TBaseBaseClass.Create;
begin
inherited Create;
// ...
// other stuff - does the work originally in TBaseClass
end;
{ TBaseClass }
constructor TBaseClass.Create;
begin
inherited Create;
// does no extra work! 'exposes' TBaseBaseClass constructor
end;
{ TNumStuff }
constructor TNumStuff.Create(num: single);
begin
inherited Create;
// ...
// other stuff
end;
constructor TNumStuff.Create(num: integer);
begin
inherited Create;
// ...
// other stuff
end;
end.
In another unit, if you put a test procedure like this
procedure Test;
var
iBaseClass : TBaseClass;
iStringStuff : TStringStuff;
iNumStuff : TNumStuff;
begin
iBaseClass := TBaseClass.Create;
iStringStuff := TStringStuff.Create;
iNumStuff := TNumStuff.Create;
end;
you will find it does not compile.
But there are a couple of 'gotchas'. If you try putting this procedure in the same unit as the original definitions it will compile. That is because the TBaseBase constructor is visible within the unit.
The second gotcha is related to the fact that the hidden constructor is parameterless, and there is a public parameterless constructor for TObject, from which all objects are descended. So if you try to create an instance of TBaseBaseClass using a constructor without parameters it will compile. It just won't use the constructor you might expect. It will use the TObject constructor.
Finally I would advise against ever trying to hamstring other programmers. By all means lead them in the right direction, but don't try and stop them doing what they want to do. With that in mind, I would not do it this way. I would make the TBaseBase constructor protected, not private. I am just showing this to answer your question.
Here is how I wold deal with your problem
First I would set BaseClass constructor to be virtual and thus allow overriding it in desendant classes
TBaseClass = class
constructor Create; virtual;
end;
Then in TStringStuff class I would change your existing constructor so that its string parameter is actually an optional parameter. This would allow you to call this constructor with or without string parameter passed to it. So now ony thing you need to do is call parent constructor with the help of inherited when no parameter was passed to consturctor or do necessary work before if string paramter was passed to the constructor.
TStringStuff = class (TBaseClass)
constructor Create(str: string = ''); override;
end;
And in TNumberStuff class you just reintroduce your overloaded constructors to be able to manage multiple posible input parameter types that can be passed to the constructor.
TNumberStuff = class (TBaseClass)
constructor Create(num: integer); reintroduce; overload;
constructor Create(num: decimal); reintorduce; overload;
end;

Can it make sense to call one overloaded constructor from another?

If I have two overloaded constructors, one without and one with parameters:
constructor Create; overload;
constructor Create(Param: TObject); overload;
If I want the code in the first one to run, does it make sense to call it within the second one? And inherited to call the parent constructor first as well?
constructor TMyType.Create(Param: TObject);
begin
inherited Create;
Create;
FParam := Param;
end;
Thanks!
If I want the code in the first one to run, does it make sense to call it within the second one And inherited to call the parent constructor first as well?
No. Because your 1st constructor should call inherited one itself, so in the end the inherited constructor would get called twice, which it most probably does not expect.
Otherwise, if your parameterless TMyType.Create() does not call inherited one, then it is hardly a proper constructor and should be just removed.
So the correct approach would be like that:
constructor TMyType.Create(Param: TObject); overload;
begin
Create();
FParam := Param;
end;
constructor TMyType.Create(); overload;
begin
inherited Create(); // for both constructors
...some common code
end;
However in Delphi there is yet another possibility.
constructor Create; overload;
constructor Create(Param: TObject); overload;
procedure AfterConstruction; override;
constructor TMyType.Create(Param: TObject);
begin
inherited Create();
FParam := Param;
end;
constructor TMyType.Create();
begin
inherited ;
... maybe some extra code
end;
procedure TMyType.AfterConstruction();
begin
inherited;
...some common code
end;
Note the difference though, when would "common code" be executed and when would do "FParam := Param;"
In the 1st way, the flow would be like
Create (Param)
..Create()
....Inherited Create()
....Common Code
..FParam := Param;
AfterConstruction (empty)
In the second the sequence would be different
Create(Param) or Create()
..Inherited Create()
..FParam := Param;
AfterConstruction
..Common Code
As you can see the order of those chunks being executed got reversed.
However maybe you don't need multiple constructors at all?
constructor TMyType.Create(const Param: TObject = nil);
begin
inherited;
... Some code
FParam := Param;
end;
Yes your code makes perfect sense and the constructor's calls do exactly what one should expect.
Delphi object model supports both constructors that call inherited constructors and constructors that do not call inherited ones.
If you are not sure try this:
program Project5;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyBase = class
constructor Create;
end;
TMyType = class(TMyBase)
constructor Create; overload;
constructor Create(Param: TObject); overload;
end;
constructor TMyBase.Create;
begin
Writeln('TMyBase.Create');
end;
constructor TMyType.Create;
begin
Writeln('TMyType.Create');
end;
constructor TMyType.Create(Param: TObject);
begin
inherited Create;
Create;
Writeln('TMyType.Create(Param)');
end;
begin
TMyType.Create(TObject.Create);
Readln;
end.

How do you override delegated method implementation?

In Delphi 2007, I am using one class to implement one of the supported interfaces of second class. This is working. The Delphi help states:
By default, using the implements keyword delegates all interface
methods. However, you can use methods resolution clauses or declare
methods in your class that implement some of the interface methods to
override this default behavior.
However, when I declare a method in my second class that has the matching signature of one of the interface methods, it isn't getting called.
I wonder if this is because I'm accessing the class through another interface when I create it.
Below is a test program that demonstrates my problem:
program Project1;
{$APPTYPE CONSOLE}
type
IInterface1 = interface
['{15400E71-A39B-4503-BE58-B6D19409CF90}']
procedure AProc;
end;
IInterface2 = interface
['{1E41CDBF-3C80-4E3E-8F27-CB18718E8FA3}']
end;
TDelegate = class(TObject)
protected
procedure AProc;
end;
TMyClass = class(TInterfacedObject, IInterface1, IInterface2)
strict private
FDelegate: TDelegate;
property Delegate: TDelegate read FDelegate implements IInterface1;
public
constructor Create;
destructor Destroy; override;
procedure AProc;
end;
procedure TDelegate.AProc;
begin
writeln('TClassDelegate.AProc');
end;
constructor TMyClass.Create;
begin
inherited;
FDelegate := TDelegate.Create;
end;
destructor TMyClass.Destroy;
begin
FDelegate.Free;
inherited;
end;
procedure TMyClass.AProc;
begin
writeln('TMyClass.AProc');
end;
var
MyObj : IInterface2;
begin
MyObj := TMyClass.Create;
(MyObj as IInterface1).AProc;
end.
When I run this I get as output:
TClassDelegate.AProc
What I want is:
TMyClass.AProc
Any help appreciated.
seems you have to redeclare your method in this way:
TMyClass = class(TInterfacedObject, IInterface1, IInterface2)
strict private
....
procedure test();
public
....
procedure IInterface1.AProc = test;
end;
procedure TMyClass.test;
begin
writeln('TMyClass.AProc');
end;
so IInterface1.AProc for TMyClass is mapped to Test() (not to FDelegate.AProc)
and result is TMyClass.AProc
The documentation explicitly states that the behaviour you see is as designed:
If the delegate property is of a class type, that class and its ancestors are searched for methods implementing the specified interface before the enclosing class and its ancestors are searched.
I guess in the full example you have an interface with multiple methods and are wanting the majority specified by the delegate, and specific ones overridden by the implementing class. I can't see how to achieve that with just one class, but it can be done if you introduce a second class:
program Project1;
{$APPTYPE CONSOLE}
type
IInterface1 = interface
['{15400E71-A39B-4503-BE58-B6D19409CF90}']
procedure AProc;
procedure AnotherProc;
end;
TDelegate = class
protected
procedure AProc;
procedure AnotherProc;
end;
TMyClass = class(TInterfacedObject, IInterface1)
strict private
FDelegate: TDelegate;
property Delegate: TDelegate read FDelegate implements IInterface1;
public
constructor Create;
destructor Destroy; override;
procedure AProc;
end;
TMyOtherClass = class(TMyClass, IInterface1)
procedure IInterface1.AProc = AProc;
end;
procedure TDelegate.AProc;
begin
writeln('TDelegate.AProc');
end;
procedure TDelegate.AnotherProc;
begin
writeln('TDelegate.AnotherProc');
end;
constructor TMyClass.Create;
begin
inherited;
FDelegate := TDelegate.Create;
end;
destructor TMyClass.Destroy;
begin
FDelegate.Free;
inherited;
end;
procedure TMyClass.AProc;
begin
writeln('TMyClass.AProc');
end;
var
MyObj: IInterface1;
begin
MyObj := TMyOtherClass.Create;
MyObj.AProc;
MyObj.AnotherProc;
Readln;
end.
As #teran points out, if you are prepared to rename your method then there is an easier solution.
It might be due to the visibility of the property. Every time I use implements the properties are protected or public, same for all the examples I could find in the VCL (eg TAutoObjectEvent.
Attempt #2:
What happens if you remove the AProc() method from TMyClass? Does it then use the one on TDelegate?
The part of documentation you mentioned seems to be outdated. If you try to use method resolution for an interface which is used in an implements clause you will get compiler error E2264: Cannot have method resolutions for interface '%s'.
The solution shown in the link above - to simply give the procedure the same name as declared in the interface - doesn't seem to work, either, in Delphi XE (it compiles but the procedure is not called).

How to access fields of a TTestCase in a TTestSetup class

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

Resources