Tuesday, March 16, 2021

Unittests in Bold. Is it possible ?

 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 😃

2 comments:

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

    ReplyDelete
  2. Michael 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.

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

    ReplyDelete