Using DUnit by Yong Yoon Kit

# Using DUnit
by Yong Yoon Kit

    020607  yky  Modified UML diagram, added some Questionsns
    020610  yky  Modified changed example to TCounter from TPDObject added UML diag.g.

## Abstract

This document will briefly introduce one aspect of eXtreme Programming (XP) which we will use in our development cycle, the DUnit. It will describe how to Use Test Harnesses to test the code we have written and demonstrate its advantages in refactoring, optimizations, peace of mind and the quality of our software.

## Introduction

DUnit orgininates from an eXtreme Programming module which tests the developers code and confirms that the code or module does what it should.

The concept is simple: write your test harness first and expect your code to fail before implementation. After implementation, ALL tests should pass. If you can think of different scenarios, write the test cases, testing for the correct results. And remember, when a bug is found, the developer will only test that bug once manually because he MUST write a test case to keep testing for that bug in the future.

On unit tests, please refer to this page:

http://www.extremeprogramming.org/rules/unittests.html

### SUnit, JUnit, CUnit, etc...

DUnit is a member of the *Unit family for all the different programming languages. It is developed as an Open Source project in sourceforge (dunit.sourceforge.net) and can be freely downloaded and used.

### Refactoring

It is also important that when we review (clean up ugly code, make things more readable) and optimize (make things faster) we do not break the software. If we have written complete test harnesses, then ALL tests should pass as before. This gives us a lot of opportunity to modify (for the better) existing code but ensuring that the process does not affect the end result.

Refactoring is this process of going back into old code, improving it, and ensuring that the results are the same if not better.

This is especially important for the case of OO Programming where changes in the Base class may have dire consequences to the inherited classes. So if we have tests written throughout the hierarchy, changes which change the behaviour of descendant classes can be detected.

### eXtreme Programming (XP)

On more about eXtreme Programming as a software development methodology, please go to www.extremeprogramming.org or http://www.xprogramming.com/xpmag/whatisxp.htm

## Example Test Harness

In this Example, we will demonstrate how to

1.create a simple class declaration without its implementation

2.setup the test harness

3.write its test cases

4.run the test harness expecting errors because of the incomplete implementation

5.implement each function one by one

6.continuously test our creation iteratively

The example is to create a simple Counting object called TCounter which takes in an input value and when functions like double, factorial, power are called, it will do the operation on the value and also return the values.

Here is the UML diagram which describes the classes inheritance, the attributes and functions we need to implement.

#### For any Class, there must be another Class to test it.

For example, TCounter will have a TCounterTest, and this should exist in a seperate application from the actual usable app.

## Setting up the Project Test Harness

Here are the steps to create a Test Harness:

1.Make a New project

2.Remove Unit1 (the default Form unit) from the Project

3.Add to the project these units: GUITestRunner and TestFrameWork which are located in crmpos/Tests/DUnit

4.Replace Application.Initialize and Application.Run with GUITestRunner.runRegisteredTests

5.Thats all you have to do, and the project code should look something like this:

    uses
      CounterCls in 'CounterCls.pas',,
      CounterTest in 'CounterTest.pas',,
      TestFramework in '..\..\..\Tests\DUnit\TestFramework.pas',
      GUITestRunner in '..\..\..\Tests\DUnit\GUITestRunner.pas' {GUITestRunner};

    {$R *.res}

    begin

    end.h

## Making a new Unit Test

If we were to test the TCounter class, we will create a new unit and call it CounterTest.pas

In it, we must use TestFrameWork from DUnit. Of course we will also use the objects which we want to test which is CounterCls.pas

    {   020607  yky  Created to illustrate the use of DUnitUnit
                       This unit will test the TCounter Class.he TCounter Class.
    }
    interface
    uses
       , ;

### Inheriting TTestCase

The first thing to do is to create a Test Case Object, and to do so, we inherit from TTestCase.

We will also then override two protected procedures called SetUp and TearDown.

Subsequent Tests will be published procedures which have names starting with 'test'.

The code should look something like this:

      TCounterTest = class(TTestCase))
      protectedd
           mCounter: TCounter;
        procedure SetUp; override;de;
        procedure TearDown; override;de;
      publishedd
        procedure testDouble;le;
           procedure testPower;
        procedure testFactorial;al;
      end;;

### SetUp

The SetUp function is called for every published test procedure in this Harness.

In this case we need a FObj instantiated every time.

    begin
      inherited;;

    end;

The FObj is usually the object we intend to test.

### TearDown

For everything that has been Created, we will need to destroy. So TearDown would look something like this:

    begin
      inherited;;

    end;

Now that we have done our housekeeping, we are ready to test the hell out of mCounter!

## Writing some tests

Before we write the tests, we have to look at the object we are testing and its specifications. We do not need to have the object implemented yet. Here is the object:

       publisheded
         property input: real;eal;
         property DoubleIt: real;eal;
         property PowerIt: real;eal;
         property FactorialIt: real;eal;
       end;d;

This object is rather basic. Given and input, it can calculate the double with DoubleIt, the power of itself with PowerIt and the factorial with FactorialIt.

Now that we know what our Class to be tested is suppose to do, we can proceed in writing the Test Harness before implementing it!

### Publishing the testDouble

The first thing to do is write a published procedure called testDouble. The Test Harness Class should look something like this:

      protectedd
        mCounter: TCounter;er;
        procedure SetUp; override;de;
        procedure TearDown; override;de;
      publishedd

        procedure testPower;er;
        procedure testFactorial;al;
        procedure testFactorialNegative;ve;
      end;;

### Using Check

Check is a DUnit function which takes in two parameters; the first is the logical operation and the second is the error message or information to report if something did go wrong.

### Test Plan for DoubleIt

This is a test plan for DoubleIt

1.Set the input to 5

2.Check that DoubleIt is 10

3.Check larger numbers of Doubleit

4.Check Fractions of double it

5.Check negative numbers of DoubleIt

    begin
      with mCounter doo
      beginn
         input := 5;= 5;
         Check( input = 5, 'input should be 5');5');
         Check( DoubleIt = 10, 'double of 5 should be 10');0');
         input := 1055;055;
         Check( DoubleIt = 2110, 'double of 1055 should be 2110');0');
         input := 11.23;.23;
         Check( abs(DoubleIt - 22.46) < 0.001,001,
                      'double should work for fractions ' + FloatToStr( DoubleIt ));Str( DoubleIt ));
         input := -23.43;.43;
         Check( abs(DoubleIt - (-46.86)) < 0.001,001,
                      'double should work for negatives too'); negatives too');
      end;;
    end;

We need to use the funny abs function < 0.001 because floating point numbers do not equate well with fractions.

### Testing Exceptions

There will definitely be cases where we purposely attempt to break the object by giving it invalid data: Exceptions would be raised during Validations or operations.

In the Counter Class code, we have defined factorial only able to accept positive numbers. Negative numbers will result in an exception being raised.

This is the Test Harness of the plan and code for testFactorialNegative:

1.Set the input value to -5

2.Attempt to get the Factorial Value

3.If an exception was not raised, then report and error in the implementation

4.If an exception called ECounterNegative was caught then the Counter class was constructed well.

Here is the code:

    begin
      with mCounter doo
      beginn
         input := -5; -5;
         try try
            Check( FactorialIt > 0, 'Factorial it should raise an exception');tion');
            Check( False, 'This should never execute.');ute.');
         exceptcept
           on ECounterNegative do Check(True, 'Negative Factorial detected OK'); OK');
         end;end;
      end;;
    end;

Notice how we use the Check function. In this case, we do not use it to test anything, but hard code True and False values into it to send messages to the DUnit interface.

We always must send a Check(False) if we do not want the execution to continue; where errors that have occurred yet no exceptions were raised.

Use Check(True) just to confirm to ourselves that the execution was correct.

## Registering TTestCase

Once we have completed writing the test harness, we will have to register this so that the GUITestRunner can run the tests. To do so, add this in the initialization section of the Unit.

       ('Tutorial/Counter', TCounterTest.Suite);
    end.

In RegisterTest, the first parameter describes the Tree or Hierarchy in the placement of the tests.

The second parameter is the TTestCase object which you have just defined. The published test procedures will be displayed in the UI

## Running the Test Harness

Just press F9 within Delphi, or run the compiled application directly, and the DUnit interface will appear. Just press the green Play button, and it will run through the tests.

If there are any Check errors, a pink result will appear. Any uncaught exceptions will be represented by red colour.

If everything goes well, then all the tests should be green.

### Implementing the Counter Class

We are now ready to implement the Counter Class. Looks something like this:

    begin
      Result := Finput * 2;;
      Finput := Result;;
    end;

    function TCounter.: real;
    var i: integer;
    begin
      if Finput < 0 thenn
              .Create('Cannot Factorial a negative number');
      Result := 1;;
      for i := 1 to trunc( Finput ) doo
        Result := Result * i; i;
      Finput := Result;;
    end;

    function TCounter.: real;
    begin
      Result := Finput * Finput;;
      Finput := Result;;
    end;

    procedure TCounter.(const Value: real);
    begin
      Finput := Value;;
    end;

We can then run DUnit again, and expect all the tests to pass.

## DUnit in production

Eventually the list of tests would be extremely long, covering all aspects of the code we have written. Daily test runs will be run in batch mode and any problems can be easily detected and fixed.

Please use DUnit extensively and update the test cases whenever you detect a new bug, think of a strange scenario or want peace of mind.

DUnit Tutorial

## Overview

This tutorial will cover the creation of a new object with specific attributes and functions. Before the implementation of this object, a test harness is written to confirm that the object works. Using this harness we can also attempt to break this object, refactor and confirm that the objects works just as before, if not better.

## Specifications

The purpose of this Tutorial is to create an object called TNumList which can do these things:

•collect an array of reals (maximum is 200 numbers)

•Add another real at the end of the list

•keep a Count / Tally of the number of Reals

•Total Up the array of Reals

•Find the Average of the Reals

•Inherites directly from TObject

Here is the UML:

## Setting up

### the Project

Refer to the “Setting up the Project Test Harness” instructions.

Create two new units and save them as NumListCls and NumListTest.

The resultant code for the project should look something like this:

    uses
      TestFramework in '..\..\..\Tests\DUnit\TestFramework.pas',,
      GUITestRunner in '..\..\..\Tests\DUnit\GUITestRunner.pas' {GUITestRunner},,
      NumListCls in 'NumListCls.pas',,
      NumListTest in 'NumListTest.pas';;

    {$R *.res}

    begin
      GUITestRunner.runRegisteredTests;;
    end.

### the NumListCls Unit file

We will have to create a skeleton class with the necessary published properties as defined in the UML. So quickly define TNumList as such, without the need to implement. Just make sure it compiles with stub code.

      privatee
          FCount: integer;
        FNums: array [0..199] of real;al;
        function GetCount: integer;er;
        function GetAverage: real;al;
        function GetTotal: real;al;
        function GetNums(ind: Integer): real;al;
        procedure SetNums(ind: Integer; const Value: real);l);
      publicc
        constructor Create;te;
        procedure   Add( pNum: real );al );
        property Nums[ ind: Integer ]: real read GetNums write SetNums;ms;
      publishedd
        property Count: integer read GetCount;nt;
        property Total: real    read GetTotal;Total;
        property Average: real  read GetAverage;age;
      end;;

!!!! DO NOT PROCEED IN IMPLEMENTING THIS TNumList CLASS !!!!

### the NumListTest Unit file

Now lets set up the Test Harness Unit. Follow the instructions in the section “Making a new Unit Test”

Your code should look like this:

    interface
    uses
       ;

    type

      TNumListTest = class(()
      protectedd
        procedure SetUp; override;de;
        procedure TearDown; override;de;
      publishedd
      end;;
        :  :

## Writing the Test Harness

Now that you have set up your 3 files, you are ready to create the tests prior to implementation. This ensures that you have understood all the requirements and you know what to expect given the pre-conditions and inputs.

Define a protected variable called mNumList of type TNumList. Make sure that your SetUp procedure creates this object and your TearDown frees it.

### Exercise 1 – implementing testAdd

The first thing we would like to test is the Add procedure.

Here is our test plan for Add:

1.Make sure the NumList has zero Count

2.Add a number like 321 in it

3.Check that the Count has increased by one (no more no less)

4.Add the next number 456

5.Check that the Count has increased again

6.Check using Nums that Nums[0] is equal to 321

7.Check that Nums[1] is 456

That should be enough to test the functionality of Add.

We need to assume that Count, SetNum and GetNum already work for the simple cases. However for more extensive tests, we need testGetNum and testSetNum

Please create a new published procedure in TNumListTest and implement this test plan.

### Exercise 2 – implementing testGetNum

Make sure that mNumList.Nums[2] works as planned. Make a test plan and implement it. It may be quite simple.

### Exercise 3 – implementing testSetNum

Make sure that mNumList.Nums[2] := 343, works as planned. Make a test plan and implement it. It may be quite simple too.

### Exercise 4 – implementing testTotal

Come up with your own test plan for testTotal, and implement it

### Exercise 5 – implementing testAverage

Come up with your own test plan for testAverage, and implement it

### Running the test Harness

Using the path of 'Tutorial/NumList', register your TestCase. See the section “Registering TTestCase” on how to do this.

Now that you have created 3 tests you can try running this application by pressing F9. Click on the green Run button, and you should see something like this:

Running this, the DUnit interface reports errors in All of our 3 test. This is as expected because we haven't implemented the TNumList object yet!

### Exercise 6 – Implementation of the TNumList

Now you can proceed in implementing this simple object. After every step, run the DUnit interface to run through the tests for instant gratification.

1.Implement Add, which increments Count after adding onto the list

2.Implement Total which totals up all the reals up to Count-1

3.Implement Average which is the totals divided by Count

### Exercise 7 – testSetNum and its implementation

Make a test procedure to test the ability of mNumList.Nums[0] to be altered

### Exercise 8 – Further Testing

We will now proceed with further testing, and this normally called

#### Breaking the object!

•What happens when we Access (Get) Nums[-1] or Nums[202] or even Nums[Count]?

•What happens when we write (Set) Nums[-1] or Nums[202] or Nums[Count]?

•What happens when we add more than 200 items in the list? (Please turn on Project/Options/Compiler/Range Checking + Overflow)

•What happens when we try to get the Average without any items in the list?

•What happens when we put very large reals in the list and find the Total? Use MaxDouble from Math

### Decide on how to handle problems

We will now need to decide how to handle these problems.

1.If this object is accessed (either Get or Set) outside its Bounds, i.e. Nums[-1] or Nums[Count] then an exception called ENumListIndexOutOfBounds should be raised

2.If we attempt to add more than 200 items, then an exception called ENumListCapacityExceeded should be raised

3.If we try to get the Average from an empty list, then the result should be zero.

4.If the total is too large for the type an exception called ENumListTotalOverflow should be raised.

### Write the new tests

Now that you have decided on the peculiar situations, write out the tests to re-create this with the proper exception handling if necessary.

### Fix the object

After the test harness has been completed, run DUnit and expect failures.

Go back to the object and build in the necessary new behaviours of the object.

## Refactoring and modifications

eXtreme Programming is about change. Customers tend to change their minds during the progress of a project. We should not encourage their changes, but we must be prepared that changes do occur.

So the new requirement are:

1.From the previous requirement of only 200 reals to be kept, we now need to keep track of an Unlimited (as much as RAM can handle) number of items

2.There should be a method to delete any real given an index

3.These numbers should be able to be sorted by Values

### Exercise 9 – Changing an Array to a TStringList

Given the 3 new requirements, we should consider changing our static Array FNums into a more dynamic TList descendant which can cater for Unlimited entries, sort functions and Deletes. The most user friendly one which comes to mind is a TStringList.

The only drawback is that we will have to convert reals to Strings, which may hinder performance slightly.

But lets go along with that idea anyway, and proceed with this plan in REFACTORING our array into a TStringList.

After we have done so, please run the DUnit checks to make sure that all the test cases work. If it does, congratulations, you have improved your code yet kept the functionality the same. Isnt encapsulation wonderful?

### Exercise 10 – Creating more tests for the new functions

Please proceed in planning and implementing tests for testDelete and testSort

## Questions

What is the parameter format of Check?

How do I test for an expected Exception?

What will DUnit show if there was an unexpected Exception?

What happens when you dont have any code in a test procedure?

Does the order of the published test procedures matter to the DUnit UI?

Do we delete the trivial tests as we move on to more complex scenarios?

~END~