I started to explore interfaces. One of the reasons I want to know more about interfaces is that I want to use unittests. Interfaces are a key part that makes it easier to make the code more testable.
I worked with Delphi for a long time and I know the concept behind interfaces but never really created new ones and used them. I believe one reason for this is that Bold generates code for classes in the model in one file businessclasses.pas. businessclasses.pas is a hotspot. It must be included whenever you want to reference a class or member in the model.
The structure of this big file is this:
(*****************************************)
(* This file is autogenerated *)
(* Any manual changes will be LOST! *)(*****************************************)
(* Generated 09.02.2021 17:51:02 *)(*****************************************)
(* This file should be stored in the *)
(* same directory as the form/datamodule *)
(* with the corresponding model *)(*****************************************)
unit BusinessClasses;
{$INCLUDE BusinessClasses_Interface.inc}
{$INCLUDE AmObject.inc}
{$INCLUDE Quantity.inc}
// and 80 other include files
procedure TBusinessClassesRootList.Add(NewObject: TBusinessClassesRoot);
begin
if Assigned(NewObject) then
AddElement(NewObject);
end;
function TBusinessClassesRootList.IndexOf(anObject: TBusinessClassesRoot): Integer;
begin
result := IndexOfElement(anObject);
end;
// And example of code for class TPlanMission in model.
// The link TPlanMission.StateInProcess can be read and changed
// The model contains thousands of these
function TPlanMission._Get_M_stateInProcess: TBoldObjectReference;
begin
assert(ValidateMember('TPlanMission', 'stateInProcess', 194, TBoldObjectReference));
Result := TBoldObjectReference(BoldMembers[194]);
end;
function TPlanMission._GetstateInProcess: TVehiclePool;
begin
Result := TVehiclePool(M_stateInProcess.BoldObject);
if (assigned(Result) and not (Result is TVehiclePool)) then
Assert(false, SysUtils.format(BoldMemberAssertInvalidObjectType, [ClassName, 'stateInProcess', Result.ClassName, 'TVehiclePool']));
end;
procedure TPlanMission._SetstateInProcess(const value: TVehiclePool);
begin
M_stateInProcess.BoldObject := value;
end;
BusinessClasses_Interface.inc contains actual class definitions.
interface
uses
AttracsAttributes,
BoldAttributes,
Classes,
SysUtils;
type
TBusinessClassesRoot = class;
TBusinessClassesRootList = class;
TAmObject = class;
TAmObjectList = class;
// About 900 more forward declarations of classes
TBusinessClassesRoot = class(TBoldObject)
private
protected
public
end;
// To make it easier to read I only left Created property in AmObject and a override method in Bold CompleteOverride
TAmObject = class(TBusinessClassesRoot)
private
function _Get_M_Created: TBADateTime;
function _GetCreated: TDateTime;
procedure _SetCreated(const NewValue: TDateTime);
protected
procedure CompleteCreate; override;
public
property M_Created: TBADateTime read _Get_M_Created;
property Created: TDateTime read _GetCreated write _SetCreated;
end;
// Definitions of other 479 classes follows here
implementation
uses
{$INCLUDE Attracs_Implementation_Uses.inc} ,
So the result can be big, really big.
Attracs have 480 classes.
BusinessClasses.pas has 88902 lines
BusinessClasses_Interface.inc has 37480 lines
Attracs_Implementation_Uses.inc has only 90 lines
Those files are generated from the model. Only inc-files like AmObject.inc, Quantuty.inc, etc with the actual code for methods can be manually edited.
So now when we know about the structure the BIG question is. How can I make unit tests for classes in businessclasses.pas ? This is the main reason I wrote this post to get some input from other developers.
I think the main problem with businessclasses.pas is the size and it is generated code.
Generally, a developer wants to split it so each unit only contains one or a couple of classes. These make the code more flexible. Attracs_Implementation_Uses.inc also include other units not related to model.
Lets take an example. Attracs_Implementation_Uses.inc contains ParcelTrackingHandling.
This is the unit
unit ParcelTrackingHandling;
interface
uses
Classes,
SysUtils,
BusinessClasses,
BusinessInterfaces;
type
TMediaHandle = class(TInterfacedObject)
private
fSendmedias: TStringList;
function SendLinkTo(aContactMedia: TContactMedia; aParcel: TParcel): Boolean;
function SendMail(const aEmailAddress: string; aParcel: TParcel): Boolean;
function SendSms(const aMobileNumber: string; aParcel: TParcel): Boolean;
function ExternalLinkPossibleToSend(aParcel: TParcel): Boolean;
function GetTrackingSubject(aParcel: TParcel): string;
procedure MayAddRole(aResults: TStringList; aContactMedia: TContactMedia);
public
constructor Create;
destructor Destroy; override;
function GetReceivingParty(aParcel: TParcel): TDepartment;
function SendExternalLink(aParcel: TParcel; aResults: TStringList): Boolean;
end;
I could make an interface for this in a new unit
unit BusinessInterfaces;
interface
uses
Classes,
BusinessClasses;
type
ITracking = interface
['{309FF866-56EC-405D-8168-D03DB8BBB8F2}']
function GetReceivingParty(aParcel: TParcel): TDepartment;
function SendExternalLink(aParcel: TParcel; aResults: TStringList = nil): Boolean;
end;
implementation
end.
But unfortunately this require BusinessClasses in the uses as TParcel that is a class in the model is used as parameter. Any idea how to avoid this chicken and egg situation ?
Input and comments are appreciated 😃
The question is: what do you want to test? Do you want to test BOLD itself - then you would need to test the framework classes that are not autogenerated. That most likely also includes some classes that are for the code generation and you can have tests that ensure they generate the proper code.
ReplyDeleteMichael Feather's Working Effectively with Legacy Code book might be helpful. There are also lots of books, videos and other testing help on the Net.
ReplyDeleteTesting generated code would be easier if you have control over what Bold generates, but using Mocking, type aliases and other techniques you should be able to test the generated classes. Nothing is impossible...