Introducing DUnitX - A new Unit Test Framework for Delphi

Since I wrote an article about Delphi unit testing I also came thorough new DUnitX test framework. And I found that it nicely uses the possibilities of newer Delphi to make work easier. Of course, We can also use old unit test framework DUnit also but we cannot test new Language/RTL features like Generics, Attributes, Anonymous methods etc.

DUnitX Overview

DUnitX is an open-source unit test framework based on the NUnit test framework, including some ideas from XUnit as well. The RAD Studio integration of DUnitX framework enables you to develop and execute tests against Win32, Win 64 and MacOSX in Delphi. It is designed to work with Delphi 2010 or later, it makes use of language/RTL features that are not available in older versions of Delphi. The DUnitX testing framework provides its own set of methods for testing conditions. You can use the provided methods to test a large number of conditions. These methods represent common assertions although you can also create your own custom assertions.

DUnitX Features
Any class can contain tests
Attribute based testing
An extensive Assert Class
Setup and TearDown per test method and per test fixture.
API Documented using Xml-Doc
Console Based Runner
XML Logging which produces output compatible with 
Cross platform currently supporting:
Win32,Win64 and OSX Compilers.
Limited backwards compatibility with DUnit test classes.
Wizard for creating new tests.

Why DUnitX and How does it differ from DUnit

Unit Testing in Delphi is not new, the DUnit framework has been around for many years. So why to use DunitX ? Because DunitX makes use of Generics, Attributes, Anonymous methods and new language/RTL features of Delphi.

Feature
DUnit
DUnitX
Base Test Class
TTestCase
None
Test Method
Published
Published or decorated with [Test]
Fixture Setup Method
NA
Decorated with [SetupFixture] or constructor
Test Setup Method
Override Setup from base class
Decorated with [Setup]
Test TearDown Method
Override Teardown from base class
Decorated with [TearDown]
Namespaces
Through registration parameter (string)
Unit Names (periods delimit namespaces)
Data driven Tests
NA
Decorated with [TestCase(parameters)]
Asserts
Check(X)
Assert class
Asserts on Containers(IEnumerable<T>)
Manual
Assert.Contains, Assert.DoesNotContain, Assert.IsEmpty
Asserts using Regular Expressions
NA
Assert.IsMatch (XE2 or later)
Stack Trace support
Jcl
Jcl, madExcept 3, madExcept 4, Eurekalog 7
Memory Leak Checking
FastMM4
FastMM4 (under construction)
IoC Container
Use Spring or other
Simple IoC container Built in
Console Logging
Built in
Built in (quiet or verbose modes)
XML Logging
Built in (own format)
Built in - Outputs NUnit compatible xml

Installation
DunitX is already integrated with newer versions of Delphi. So you will get DunitX folder under Delphi installation folder. C:\Program Files x86)\ Embarcadero\ Studio\19.0\source\DunitX Or we can install manually on our own. For this we can download DunitX files from following link and have to follow the installation steps. 

Wizard Installation
DunitX wizards helps us to create Test Projects and Test Cases easily. So to install DunitX wizards from the .\DUnitX\Expert folder, open the appropriate DUnitX_IDE_Expert_.dproj file. (Versions 2010 and upwards are included). First Build the project, then right click in the project manager and Install the package. Need to add the the .pas files in the projects search path if asked to.

Getting Started
Developing Delphi Application whose functions to be tested. The following sample Delphi program defines two functions that perform a simple addition and subtraction:

 Unit CalcUnit;
 interface
 type
 { TCalc }
   TCalc = class
   public
     function Add(x, y: Integer): Integer;
     function Sub(x, y: Integer): Integer;
   end;
 implementation
 { TCalc }
 function TCalc.Add(x, y: Integer): Integer;
 begin
   Result := x + y;
 end;
 function TCalc.Sub(X, Y: Integer): Integer;
 begin
   Result := x - y;
 end;
 end.

Developing Tests with DUnitX with Wizards
The structure of a unit test largely depends on the functionality of the class and method you are testing. The Unit Test Wizards generate skeleton templates for the test project, setup and teardown methods, and basic test cases. You can then modify the templates, adding the specific test logic to test your particular methods. The following describes the procedure for creating a DUnitX project for Delphi or C++Builder.

To create a DUnitX project
1. Click File > New > Other .
2. Select the DUnitX folder:
For Delphi, go to Delphi Projects > DUnitX


3. The New DUnitX Project Wizard opens. Select the required options:
Add to Existing Project Group: to add the DUnitX project to an existing project group.
Create Test Unit: to create a new Test Unit.
When you select to create a new test unit, you can set up the Test Unit Options:
Create Setup and TearDown Methods: includes the declaration and empty definition for the SetUp and TearDown methods in the Test Unit template.
Create Sample Test Methods: includes the declaration and empty definition for the sample Test Methods (Test1 and Test2) in the Test Unit template.
TestFixture Class Name: type the class name. The default name is TMyTestObject.


4. Click OK.

After finish Test Project will be look as follow

Program Project1;
{$IFNDEF TESTINSIGHT}
{$APPTYPE CONSOLE}
{$ENDIF}{$STRONGLINKTYPES ON}

uses
  System.SysUtils,
  {$IFDEF TESTINSIGHT}
  TestInsight.DUnitX,
  {$ENDIF }
  DUnitX.Loggers.Console,
  DUnitX.Loggers.Xml.NUnit,
  DUnitX.TestFramework,
  Unit1 in 'Unit1.pas';

var
  runner : ITestRunner;
  results : IRunResults;
  logger : ITestLogger;
  nunitLogger : ITestLogger;
begin
{$IFDEF TESTINSIGHT}
  TestInsight.DUnitX.RunRegisteredTests;
  exit;
{$ENDIF}
  try
    //Check command line options, will exit if invalid
    TDUnitX.CheckCommandLine;
    //Create the test runner
    runner := TDUnitX.CreateRunner;
    //Tell the runner to use RTTI to find Fixtures
    runner.UseRTTI := True;
    //tell the runner how we will log things
    //Log to the console window
    logger := TDUnitXConsoleLogger.Create(true);
    runner.AddLogger(logger);
    //Generate an NUnit compatible XML File
    nunitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile);
    runner.AddLogger(nunitLogger);
    runner.FailsOnNoAsserts := False; //When true, Assertions must be made during tests;

    //Run tests
    results := runner.Execute;
    if not results.AllPassed then
      System.ExitCode := EXIT_ERRORS;

    {$IFNDEF CI}
    //We don't want this happening when running under CI.
    if TDUnitX.Options.ExitBehavior = TDUnitXExitBehavior.Pause then
    begin
      System.Write('Done.. press <Enter> key to quit.');
      System.Readln;
    end;
    {$ENDIF}
  except
    on E: Exception do
      System.Writeln(E.ClassName, ': ', E.Message);
  end;
end.

To create a DUnitX unit
1. Choose File > New > Other .
2. Select the DUnitX folder:
For Delphi, go to Delphi Projects > DUnitX
3. Select DUnitX Unit, and then click OK. The New DUnitX Unit Wizard opens.


4. To configure the test unit template, set up the Test Unit Options:
Create Setup and TearDown Methods: includes the declaration and empty definition for the SetUp and TearDown methods in the Test Unit template.
Create Sample Test Methods: includes the declaration and empty definition for the sample Test Methods (Test1 and Test2) in the Test Unit template.
TestFixture Class Name: type the class name. The default name is TMyTestObject.
5. Click OK.

To write a Test
1. Add code to the SetUp and TearDown methods in the DUnitX unit.
2. Add asserts to the test methods.

The following example shows the test unit that you need to modify to test the two functions. You have to add code to the SetUp and TearDown methods as well as to the test methods TestAddand TestSub. Finally, make use of attributes to test the functions.

Unit TestCalcUnit;

interface
uses
  DUnitX.TestFramework, CalcUnit;

type

  [TestFixture]
  TestTCalc = class(TObject)
  strict private
    aTCalc: TCalc;
  public
    [Setup]
    procedure Setup;
    [TearDown]
    procedure TearDown;
    // Sample Methods
    // Test with TestCase Atribute to supply parameters.
    [TestCase('TestA','8,2,10')]
    procedure TestAdd(Value1, Value2, _Result: Integer);
    // Test with TestCase Atribute to supply parameters.
    [TestCase('TestB','3,4,-1')]
    procedure TestSub(Value1, Value2, _Result: Integer);
  end;

implementation

procedure TestTCalc.Setup;
begin
  aTCalc := TCalc.Create;
end;

procedure TestTCalc.TearDown;
begin
  aTCalc := nil;
end;

procedure TestTCalc.TestAdd(Value1, Value2, _Result: Integer);
var
  R: Integer;
begin
  R := aTCalc.Add(Value1, Value2);
  Assert.AreEqual(R, _Result);   // testcode
end;

procedure TestTCalc.TestSub(Value1, Value2, _Result: Integer);
var
  R: Integer;
begin
  R := aTCalc.Sub(Value1, Value2);
  Assert.AreEqual(R, _Result);  // testcode
 end;

initialization
  TDUnitX.RegisterTestFixture(TestTCalc);
end.

DUnitX Functions
DUnitX provides Assert class with a number of functions that you can use in your tests.

The following table shows some of these functions.

Function
Description
Pass
Checks that a routine works.
Fail
Checks that a routine fails.
AreEqual
Checks to see if items are equal.
AreNotEqual
Checks to see if items are not equal.
AreSame
Checks to see that two items have the same value.
AreNotSame
Checks to see that two items do not have the same value.
Contains
Checks to see if the item is in a list.
DoesNotContain
Checks to see if the item is not in a list.
IsTrue
Checks that a condition is true.
IsFalse
Checks that a condition is false.
IsEmpty
Checks to see if the value of an item is empty.
IsNotEmpty
Checks to see if the value of an item is not empty.
IsNull
Checks to see that an item is null.
IsNotNull
Checks to see that an item is not null.
WillRaise
Checks to see if the method will raise an exception.
StartsWith
Checks if a string starts with a specified substring.
InheritsFrom
Checks if a class is descendant of a specified class.
IsMatch
Checks if the item matches with a specified pattern.

DUnitX Test Runners
A test runner allows you to run your tests independently of your application. In a DUnitX test project, the test runner code from the DUnitX framework is compiled directly into the generated executable making the test project itself a test runner. The integrated DUnitX framework provides two test runners:

The Console Test Runner:
Sends all test output to the console. This is the testing mode by default when you run your DUnitX project. DUnitX framework provides TDUnitXConsoleLogger class to run the testing output to the console.
Is useful when you want to run completed code and tests from automated build scripts.


The GUI Test Runner:
Displays test results interactively in a GUI window. DUnitX framework provides a TDUnitXGuiLoggerForm class for the VCL GUI  and a TGUIXTestRunner class to generate the FireMonkey GUI output.
Is very useful when actively developing unit tests for the code you are testing.



To run a Test case in the GUI Test Runner
You should tell the runner how you will log the results. If you want to display the output using a GUI window, you should use the appropriate classes that DUnitX provides. So change the .dpr Project File code as per required.

For Console Runner

program DUnitXConsoleAPP
{$APPTYPE CONSOLE}
uses
  System.SysUtils,
  DUnitX.Examples.General, 
  DUnitX.ConsoleWriter.Base, 
  DUnitX.DUnitCompatibility,
  DUnitX.Generics in ,
  DUnitX.InternalInterfaces,
  DUnitX.IoC ,
  DUnitX.Loggers.Console,
  DUnitX.Loggers.Text in,
  DUnitX.Loggers.XML.NUnit,
  DUnitX.Loggers.XML.xUnit,
  DUnitX.MacOS.Console,
  DUnitX.Test,
  DUnitX.TestFixture,
  DUnitX.TestFramework,
  DUnitX.TestResult,
  DUnitX.RunResults,
  DUnitX.TestRunner,
  DUnitX.Utils,
  DUnitX.Utils.XML,
  DUnitX.WeakReference,
  DUnitX.Windows.Console,
  DUnitX.StackTrace.EurekaLog7,
  DUnitX.Examples.EqualityAsserts,
  DUnitX.FixtureResult,
  DUnitX.Loggers.Null,
  DUnitX.MemoryLeakMonitor.Default,
  DUnitX.Extensibility,
  DUnitX.CommandLine.OptionDef,
  DUnitX.CommandLine.Options,
  DUnitX.CommandLine.Parser,
  DUnitX.FixtureProviderPlugin,
  DUnitX.Timeout,
  DUnitX.Attributes,
  Unit1 in 'Unit1.pas';
var
  runner : ITestRunner;
  results : IRunResults;
  logger : ITestLogger;
  nunitLogger : ITestLogger;
begin
  try
    //Create the runner
    runner := TDUnitX.CreateRunner;
    runner.UseRTTI := True;
    //tell the runner how we will log things
    logger := TDUnitXConsoleLogger.Create(true);
    nunitLogger := TDUnitXXMLNUnitFileLogger.Create;
    runner.AddLogger(logger);
    runner.AddLogger(nunitLogger);
    //Run tests
    results := runner.Execute;
    System.Write('Done.. press <Enter> key to quit.');
    System.Readln;
  except
    on E: Exception do
      System.Writeln(E.ClassName, ': ', E.Message);
  end;
end.

For GUI VCL Runner

program DUnitXVCLGUIAPP;
{$R *.res}
uses
  Vcl.Forms,
  System.SysUtils,
  DUnitX.Examples.General,
  DUnitX.Loggers.Text,
  DUnitX.Loggers.XML.NUnit,
  DUnitX.Loggers.XML.xUnit,
  DUnitX.MacOS.Console,
  DUnitX.Test,
  DUnitX.TestFixture,
  DUnitX.TestFramework,
  DUnitX.TestResult,
  DUnitX.RunResults,
  DUnitX.TestRunner,
  DUnitX.Utils,
  DUnitX.Utils.XML,
  DUnitX.WeakReference,
  DUnitX.Windows.Console,
  DUnitX.StackTrace.EurekaLog7,
  DUnitX.Examples.EqualityAsserts,
  DUnitX.Loggers.Null,
  DUnitX.MemoryLeakMonitor.Default,
  DUnitX.AutoDetect.Console,
  DUnitX.ConsoleWriter.Base,
  DUnitX.DUnitCompatibility,
  DUnitX.Extensibility,
  DUnitX.Extensibility.PluginManager,
  DUnitX.FixtureProviderPlugin,
  DUnitX.FixtureResult,
  DUnitX.Generics,
  DUnitX.InternalInterfaces,
  DUnitX.IoC,
  DUnitX.Loggers.Console,
  DUnitX.CommandLine.OptionDef,
  DUnitX.CommandLine.Options,
  DUnitX.CommandLine.Parser,
  DUnitX.OptionsDefinition,
  DUnitX.Banner,
  DUnitX.FilterBuilder,
  DUnitX.Filters,
  DUnitX.CategoryExpression,
  DUnitX.TestNameParser,
  DUnitX.Loggers.GUI.VCL,
  DUnitX.Examples.AssertFailureCompare,
  DUnitX.Timeout,
  DUnitX.Exceptions,
  DUnitX.ResStrs,
  DUnitX.Examples.UITest,
  Unit1 in 'Unit1.pas';
begin
  Application.Initialize;
  Application.Title := 'DUnitX';
  Application.CreateForm(TGUIVCLTestRunner, GUIVCLTestRunner);
  Application.Run;
end.

How to convert DUnit Tests to DUnitX
Because of Delphi's old version compatibility conversion from DUnit to DUnitX is relatively simple. DUnit.DUnitCompatibility.pas has a TTestCase class with all the Check(X) methods on it. They are marked as deprecated, which delegates to the new Assert method.

To convert from DUnit code to DUnitX follow these steps:
1. In the uses clause, replace TestFramework with DUnitX.TestFramework,DUnitX.DUnitCompatibility;
2. Change the registration to TDUnitX.RegisterTestFixture (TYourTestClass)


Comments

Post a Comment

Popular posts from this blog

ShellExecute in Delphi

MS Excel Automation in Delphi

Drawing Shapes in Delphi