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 😃

Sunday, February 21, 2021

Bold big steps


Now when Bold framework are open source we have the possibility to decide in what direction we want. Bold for Sydney is next obvious step. So far Daniel Mauric have done most of the development in Attracs version of Bold so far. Both bugfixes, optimizations and new features. For example our version don't require calls to defaultsubscribe in codederived members. This make code more clean and readable.

But with open source we can cooperate as there are many smart developers  in Bolds community. One example is Yuri that solved the problem with x64. There was a runtime exception that was challenging to fix. Thanks Yuri! I have four ideas here for improvements in Bold. I am not sure if they are doable. I am humble ðŸ˜Š

  1. Threads

With Bold you don't need to write SQL to fetch data from database. Bold generate needed SQL and execute it whenever code needs it. We call it lazy fetch. Disadvantage are that it is slow as SQL is executed one by one in serie. Performance can be improved by prefetch data before it is needed. To call TBoldlist.EnsureObjects is one way. We have even an improvement called spanfetch that Bolds author Jan Norden wrote for us 10 years ago. It try to improve performance by collect access for each class before it is needed in derived members. But the generated SQL is still executed in serie in main thread. So the hourglass is common if there is much data to load. I suggesting have a pool of threads and let them execute SQL instead. It has some advantages.

  • Hourglass is gone and main thread is responsive all the time.
  • Better performance as each SQL is executed in own thread in parallell.
  • Possible to cancel current loading. Just terminate threads that executes SQL.

  1. Replace inc-files 

Code from modelled methods are in inc-files. Class definitions are in a separate inc-file. They are then references from businessclasses.pas. It works well but has some disadvantages for a developer. Most tools assume that code is only in pas-files. An example is Codeinsight in Delphi. It works not so good in inc-files. But also other tools that handle code like analysera have problem.

If you load businessclasses.pas Delphi Sydney be prepared to wait. Much slower than previous releases. I suppose it is because the new language server protocol for Codeinsight. It parse the file when loaded on IDE. Our model has about 460 classes so it try to parse the whole model which of course is slow. If classes can be arranged in ordinary pas-files instead.

Advantages:

  • Smaller files don't need to load all at once so LSP don't need to parse so much when load businessclasses.pas.
  • Tools like Codeinsight and code analyzers works better.

This means code generation needs to be changed to generate code to pas-files instead.

  1. Interfaces

Unittesting with Bold isn't easy. If I want to test class A but that have links to class B that have links to class C. We want to test class A in isolation. Best way to achieve that is to use interfaces instead of direct links to other classes. This means code generation need to be changed to generate interfaces for links.


  1. OCL syntax checks

Visual Studio can syntax check LINQ if I understand it correct in compile time. Could we make similar checks with OCL? BoldHandle already have methods to validate OCL. But only in runtime.


https://stackoverflow.com/questions/5681726/how-to-get-compile-time-checking-for-value-types-in-linq-lambdas-expression-tree