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 😃