Structure

Created Oct, 2003, by Charlie Calvert, published under the MPL

These two examples show the basic structure of two very simple applications that support DUnit tests. In the first example, DiffUnit, your code is in one unit and the test code in a second unit. The second project, called SameUnit, shows an alternative structure in which your code and the test code appear in the same unit. Though the first technique is more common, which method you prefer is a matter of taste.

DiffUnit

In this example the source code to be tested is stored in Unit1.pas and Unit2.pas. The code to test these two units are stored in Unit1Test.pas and Unit2Test.pas.

Both units to be tested are extremely short. In Listing 1 and Listing 2, you can see Unit2.pas and Unit2Test.pas. The other two units are virtually identical to these two, but perhaps not quite as interesting.

Listing 1: Simple class with one empty method stored in Unit1.pas

unit Unit2;
interface

type
  TSuperObject = class(TObject)
  public
    function DoSomethingSuper: boolean;
  end;
implementation

{ TSuperObject }

function TSuperObject.DoSomethingSuper: boolean;
begin
  // do something ...
  Result := (Random < 0.5);
end;
end.
 

Listing 2: Simple class for testing Unit2.pas

unit Unit2Test;
interface

uses
  TestFramework, Unit2;

type
  TTestSuperObject = class(TTestCase)
  private
    FSuperObject: TSuperObject;
  public
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure testSuperObject;
  end;
implementation

{ TTestSuperObject }

procedure TTestSuperObject.Setup;
begin
  FSuperObject := TSuperObject.Create;
end;
procedure TTestSuperObject.TearDown;
begin
  FSuperObject.Free;
end;
procedure TTestSuperObject.testSuperObject;
begin
  check(FSuperObject.DoSomethingSuper);
end;
initialization
  RegisterTest('', TTestSuperObject.Suite);
end.

Unit2.pas has one class called TSuperObject which features one method called DoSomethingSuper. The method returns a boolean value which should be set to true 50% of the time:

Result := (Random < 0.5);

Unit2Test contains a class called TTestSuperObject. It has one field, called FSuperObject, which is of the same type as the object found in Unit2.pas. The Setup method in TTestSupertObject is designed to create an instance of TSuperObject. The TTestSuperObject.TearDown method frees the instance of TSuperObject. The Setup method will be called before each test, and the TearDown method will be called just after each test.

TTestSuperObject has only one test case called testSuperObject. It uses the built in DUnit check method to confirm that TSuperObject.DoSomethingSuper returns true. If it does return true the test passes, if it returns false the test fails. Since DoSomethingSuper returns True 50% of the time, then this test should succeed fifty percent of the time.

The only other code in the unit that you need to examine appears in the initialization section at the bottom of the unit. There you can see the code that registers the test you have created:

RegisterTest('', TTestSuperObject.Suite);

Once you have regtistered the test, then you can write the following code in your DPR file to run the test:

TGUITestRunner.RunTest(RegisteredTests);

The entire DPR file is shown in Listing 3.

Listing 4: The DPR file for launching your test program.

{$IFDEF LINUX}
{$DEFINE DUNIT_CLX}
{$ENDIF}

{ Define DUNIT_CLX for project to use D6+ CLX }

{ Must do a BUILD ALL to compile in the test code under the TESTING directive }
program Project1Test;

uses
{$IFDEF DUNIT_CLX}
  QGUITestRunner,
{$ELSE}
  GUITestRunner,
{$ENDIF}
  TestFramework,
  Unit1,
  Unit2;

{$R *.res}

begin
  TGUITestRunner.RunTest(RegisteredTests);
end.

This example program is simple, but it teaches you most of what you need to know about writing test cases. In particular, you should note the Setup and TearDown methods designed to create and destroy the class you want to test. The testSuperObject method tests the object that we have instantiated's sole method. We know that it is a test case because it begins with the word test. The word test clues DUnit into the fact that this is a method that contains a test.

SameUnit

The code for the SameUnit example appears in a single file. Both the test code and the class to be tested are found in one unit. Listing 4 shows what the code looks like.

Listing 4: An example of placing the test code and the source code in the same unit.


unit Unit1;

interface

uses
  {$IFDEF TESTING}
  TestFramework,
  {$ENDIF}
  Classes, SysUtils;

type
  TMyObject = class(TObject)
  public
    procedure DoSomething;
  end;

  {$IFDEF TESTING}
  TTestMyObject = class(TTestCase)
  private
    FMyObject: TMyObject;
  public
    procedure Setup; override;
    procedure TearDown; override;
  published
    procedure testMyObject;
  end;
  {$ENDIF}

implementation

{ TMyObject }

procedure TMyObject.DoSomething;
begin
  // do something
end;

{$IFDEF TESTING}
function Suite: ITestSuite;
begin
  Result := TTestSuite.Create('Test MyObject');
  Result.AddTest(TTestMyObject.Create('testMyObject'));
end;

{ TTestMyObject }

procedure TTestMyObject.Setup;
begin
  FMyObject := TMyObject.Create;
end;

procedure TTestMyObject.TearDown;
begin
  FMyObject.Free;
end;

procedure TTestMyObject.testMyObject;
begin
  try
    FMyObject.DoSomething;
  except
    check(false);
  end;
end;
{$ENDIF}

initialization
  {$IFDEF TESTING}
    RegisterTest('', TTestMyObject.Suite);
  {$ENDIF}

end.

This code is essentially identical to the code in the DiffUnit example. Again we have a simple class to test, and a simple test object that tests the classes' sole method. The only difference is that the code for testing is surrounded by conditional compilation syntax:

{$IFDEF TESTING}

In Delphi you should normally choose Project | Options from the Delphi menu, then turn to the Directories/Conditional page and define the word TESTING in the Conditional Defines section. Alternatively, you can define the value directly in your source:

{$DEFINE TESTING}

In either case you should completely rebuild your code when you add or remove the conditional define.