Introduction to unit testing with Lazarus

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!

5 thoughts on “Introduction to unit testing with Lazarus

  1. I know this is an introduction so you’re trying to keep things simple but one test per public method of a production class isn’t very realistic. To get good coverage you’d need at least one test for every logic branch your production code makes.

    Also its worth point out that learning unit testing is much easier when your production code is designed to be tested. Most legacy code isn’t. How do you design your production code to be testable? Write the test first then design your production code to pass the test.

    Retrofitting existing production code with unit tests is much more difficult and time consuming. Its still worth while but I usually limit this activity to when I’m fixing defects. I’ll usually follow these steps:
    1. Write a test that exposes a problem and run it
    2. Identify the code where the problem is occurring
    3. Locate any other code that depends on the problem code
    4. Write some tests for the dependent code and run them. (Ensure they pass)
    5. Fix the problem code
    6. Rerun all tests.

    If all goes well all the tests you wrote should pass.

    Just remember unit testing doesn’t guarantee that your code is defect free but it does verify that you’ve fixed known defects.

    • I prefer “1 test = 1 published method of TTestCase” approach. When encountered a function with many branches to be tested I write a helper class with methods for each branch, and use a published method of TTestCase as a wrapper for helper class methods. I also use logging to detect what branch failed.
      The alternative is to create separate TTestCase classes for complicated functions of a unit under test – it has its pros because test cases are supported by the framework.
      Writing unit tests for legacy code is much more difficult indeed.

  2. Pingback: Some links to Delphi Unit Testing history « The Wiert Corner – irregular stream of stuff

  3. Thanks for the helpful information. I guess they’ve made some changes to Lazarus since you posted this. I found that after creating the new FPCUnit Test Application and trying to run it, I got the compiler error message “Could not find unit GuiTestRunner”. After flailing about for a bit, I found the solution is to go into Project Inspector and add fpcunittestrunner as a requirement. After that everything worked great!

Leave a reply to Kenneth Cochran Cancel reply