Introduction to unit testing with Lazarus

5

1. Lazarus 1.0 comes with built-in unit testing framework called FPCUnit. FPCUnit is another Pascal clone of Java JUnit framework, like DUnit framework supplied with Delphi, but different from DUnit in some details.
If you are absolutely new to unit testing (or to Lazarus, like me) create your first unit test project by running the wizard. Select File->New… from IDE menu, choose FPCUnit Test Application and click ‘OK’:

Check two checkboxes and click ‘OK’ in the next dialog:

The wizard is very primitive, but you need not anything more. You can save the generated unit test project as a template and never run the wizard again.

2. Now time add a unit under test to the project. Most usual unit under test is a class, but it also can be a record with methods, or a pascal unit with flat procedures in interface section. For demonstration purposes I have written TCalc class implementing a simple calculator to be a unit under test:

unit Calcs;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils;

type
  TCalc = class
  private
    FAccumulator: Integer;
  public
    procedure Clear;
    procedure Add(Value: Integer);
    procedure Sub(Value: Integer);
    property Accumulator: Integer read FAccumulator;
  end;

implementation

{ TCalc }

procedure TCalc.Clear;
begin
  FAccumulator:= 0;
end;

procedure TCalc.Add(Value: Integer);
begin
  FAccumulator:= FAccumulator + Value;
end;

procedure TCalc.Sub(Value: Integer);
begin
  FAccumulator:= FAccumulator - Value;
end;

end.

3. To implement a testing of a unit we create a test case. Our generated unit test project already contains one test case – that is TTestCase1 class. Our unit under test has 3 functions to be tested: the methods TCalc.Clear, TCalc.Add and TCalc.Sub. Edit testcase1.pas unit as follows:

unit TestCase1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fpcunit, testutils, testregistry, Calcs;

type
  TTestCase1= class(TTestCase)
  private
    FCalc: TCalc;
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure Clear;
    procedure Add;
    procedure Sub;
  end;

implementation

procedure TTestCase1.SetUp;
begin
  FCalc:= TCalc.Create;
end;

procedure TTestCase1.TearDown;
begin
  FCalc.Free;
end;

procedure TTestCase1.Clear;
begin
  FCalc.Clear;
  AssertEquals(FCalc.Accumulator, 0);
end;

procedure TTestCase1.Add;
begin
  FCalc.Add(42);
  AssertEquals(FCalc.Accumulator, 42);
end;

procedure TTestCase1.Sub;
begin
  FCalc.Sub(42);
  AssertEquals(FCalc.Accumulator, -42);
end;

initialization
  RegisterTest(TTestCase1);
end.

4. Our first unit test project is ready now. Run it and see the result:

5. We created a published method of a test case for every tested function of a unit under test. Unit testing framework includes published methods of test case class into a test runner application. Notice that Setup and TearDown methods of a test case are called every time a published method of a test case is called. When I started to use unit testing I thought that Setup (TearDown) is called when test case class is created (destroyed) – that is not true, they are called ‘per function’.

6. You can add more units under test to a unit test project. If you have several units under test (and correspondent test cases) in a project you can have a separate test register unit. Remove the initialization section from the test case unit, add a second unit testcase2.pas with TTestCase2 test case class (you can make a replica of the first) to the project, and create a new RegTests.pas unit in to register test cases:

unit RegTests;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, testregistry, testcase1, testcase2;

implementation

initialization
  RegisterTest(TTestCase1);
  RegisterTest(TTestCase2);
end.

Now we have a unit test project with 2 test cases:

7. If your unit test project grows one day you will want to have a hierarchical structure of test cases instead of a flat one. You can create a test hierarchy using a different RegisterTest overload. Create an additional hierarchy levels by updating RegTests.pas unit as follows:

unit RegTests;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, testregistry, testcase1, testcase2;

implementation

initialization
  RegisterTest('Level1.Level2', TTestCase1);
  RegisterTest('Level1.Level2', TTestCase2);
end.

8. Our oversimplified test case class contains all testing code inside the published methods. More realistic published methods are wrappers for other functions which perform actual testing. You may be tempted to add private methods and fields to test case classes; it is OK if your test case class is simple, otherwise you will soon find your test case class cluttered with a mess of methods corresponding to different tests (published methods). My own experience lead me to creating (if needed) a separate helper class for every test. These helper classes inherit from a common base class which provides access to TTestCase functions:

type
  TTestHelper = class
  private
    FTestCase: TTestCase;
  public
    constructor Create(ATestCase: TTestCase);
    property TestCase: TTestCase read FTestCase;
  end;

implementation

constructor TTestHelper.Create(ATestCase: TTestCase);
begin
  FTestCase:= ATestCase;
end;

Happy unit testing with FPCUnit and Lazarus!

Advertisements

New EULA on the next morning

7

To repeat the old news, here is an extract from the recent EULA leakage:

.. the use of data access technologies for client/server
connectivity will no longer be allowed in the Professional edition.
This includes both Embarcadero and 3rd party solutions. Professional
users may only, legally, access local databases with their applications.

Users who want to use client/server database access can purchase a
Client/Server Add-On Pack for their Professional edition or purchase
an Enterprise, Ultimate or Architect edition product.

This restriction if for new licenses only. Users upgrading to XE3
will be “grandfathered” in that they will be able to continue to use
3rd party data access technologies for client/server database access
in version XE3 ..

and also a clarification by DavidI from non-technical:

if you are a Delphi XE2 to Delphi 1 customer you are unaffected by this EULA update

that means only those who are about to extend their business and buy new licences are affected by this EULA change.

So for most people the problem is not money, the problem is Delphi reputation in Delphi ecosystem and among software developers in general. The new EULA actually prohibits to link dynamically some client dll’s, like ‘fbclient.dll’ in case of Firebird. That is ridiculous. Any decent software development tool can do it, and there is no way to cut off this function from the compiler. Dll linking is undoubtably a fair use of any compiler, including the one from Starter edition. Restrictions of this kind are simply unacceptable, and I guess they will be considered illegal in many countries.

It is not yet too late to stop this nonsense.

‘var’ vs ‘out’ as a function argument

5

The Delphi Documentation is known to be a confusing and untrustful source of information. I am not quoting the current documentation content here and hope it will be fixed – right now it is a nonsense.

You can replace ‘var’ by ‘out’ and get binary identical code if only POD types are used.

Still the ‘out’ keyword is not an alias for ‘var’. The difference appears when lifetime managed types (strings, dynamic arrays, interfaces) are involved:

program Test1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TIntArray = array of Integer;

var
  S: string;
  II: IInterface;
  A: TIntArray;
  Obj: TObject;

procedure TestString(out AStr: string);
begin
  Assert(AStr = '');
  Writeln('Passed');
end;

procedure TestInterface(out AII: IInterface);
begin
  Assert(AII = nil);
  Writeln('Passed');
end;

procedure TestDynArray(out AArr: TIntArray);
begin
  Assert(AArr = nil);
  Writeln('Passed');
end;

procedure TestObject(out AObj: TObject);
begin
  Assert(AObj = nil);  // Failed
  Writeln('Passed');
end;

begin
  try
    try
      S:= '123';
      TestString(S);
      II:= TInterfacedObject.Create;
      TestInterface(II);
      A:= TIntArray.Create(42);
      TestDynArray(A);
      Obj:= TObject.Create;
      TestObject(Obj);
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    Obj.Free;
    ReadLn;
  end;
end.

The above code shows that when you use ‘out’ instead of ‘var’ in a function declaration and pass a reference counted instance the compiler makes the input value of an instance unavailable in a function. The ‘out’ keyword guaranties that the input value of a reference counted instance will never be changed. That is the only difference between ‘var’ and ‘out’ parameters I could find. The 4th test (procedure TestObject) shows that ‘out’ does not protect input value of an object (non-refcounted instance).
Sometimes use of ‘out’ keyword leads to unexpected results (the next example is taken from Alex Ciobanu blog):

procedure CopyStr(const AStr: String; out AOutStr: String);
begin
  AOutStr := AStr;
end;

var
  SomeStr: String;

begin
  SomeStr := 'Hello World';
  CopyStr(SomeStr, SomeStr);
  WriteLn(SomeStr);
  ReadLn;
end.