0% found this document useful (0 votes)
79 views14 pages

Tutorial

This document introduces the testing framework DUnit and how to use it to write unit tests. It provides an example of creating a TCounter class and corresponding TCounterTest class to test it. Key steps include: 1. Creating a test project and adding DUnit units 2. Inheriting from TTestCase and overriding SetUp and TearDown methods 3. Writing test methods like testDoubleIt that use Check to validate results 4. Testing exceptions by checking specific errors are raised as expected The example demonstrates writing tests before implementing the class to drive development and ensure all tests pass with each change.

Uploaded by

wilker3
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
79 views14 pages

Tutorial

This document introduces the testing framework DUnit and how to use it to write unit tests. It provides an example of creating a TCounter class and corresponding TCounterTest class to test it. Key steps include: 1. Creating a test project and adding DUnit units 2. Inheriting from TTestCase and overriding SetUp and TearDown methods 3. Writing test methods like testDoubleIt that use Check to validate results 4. Testing exceptions by checking specific errors are raised as expected The example demonstrates writing tests before implementing the class to drive development and ensure all tests pass with each change.

Uploaded by

wilker3
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 14

Using DUnit

by Yong Yoon Kit

020606 yky Created


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

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.

TObject TTestCase
#SetUp
#TearDown
+Check( test: Bool; msg: str)

TCounter TCounterTest

+ input: real #mCounter #SetUp


#TearDown
+ DoubleIt: real + testDoubleIt
+ PowerIt: real + testPowerIt
+ FactorialIt: real + testFactorialIt

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:

program CounterObjectTest;

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
GUITestRunner.runRegisteredTests;
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

unit CounterTest;
{ 020607 yky Created to illustrate the use of DUnit
This unit will test the TCounter Class.
}
interface
uses
TestFrameWork, CounterCls;

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:

type
TCounterTest = class(TTestCase)
protected
mCounter: TCounter;
procedure SetUp; override;
procedure TearDown; override;
published
procedure testDouble;
procedure testPower;
procedure testFactorial;
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.

procedure TCounterTest.SetUp;
begin
inherited;
mCounter := TCounter.Create;
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:

procedure TCounterTest.TearDown;
begin
inherited;
mCounter.Free;
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:

TCounter = class(TObject)
published
property input: real;
property DoubleIt: real;
property PowerIt: real;
property FactorialIt: real;
end;

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:

TCounterTest = class(TTestCase)
protected
mCounter: TCounter;
procedure SetUp; override;
procedure TearDown; override;
published
procedure testDouble;
procedure testPower;
procedure testFactorial;
procedure testFactorialNegative;
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

procedure TCounterTest.testDouble;
begin
with mCounter do
begin
input := 5;
Check( input = 5, 'input should be 5');
Check( DoubleIt = 10, 'double of 5 should be 10');
input := 1055;
Check( DoubleIt = 2110, 'double of 1055 should be 2110');
input := 11.23;
Check( abs(DoubleIt - 22.46) < 0.001,
'double should work for fractions ' + FloatToStr( DoubleIt ));
input := -23.43;
Check( abs(DoubleIt - (-46.86)) < 0.001,
'double should work for 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:

procedure TCounterTest.testFactorialNegative;
begin
with mCounter do
begin
input := -5;
try
Check( FactorialIt > 0, 'Factorial it should raise an exception');
Check( False, 'This should never execute.');
except
on ECounterNegative do Check(True, 'Negative Factorial detected OK');
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.
initialization
RegisterTest('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:

function TCounter.GetDoubleIt: real;


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

function TCounter.GetFactorialIt: real;


var i: integer;
begin
if Finput < 0 then
raise ECounterNegative.Create('Cannot Factorial a negative number');
Result := 1;
for i := 1 to trunc( Finput ) do
Result := Result * i;
Finput := Result;
end;

function TCounter.GetPowerIt: real;


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

procedure TCounter.Setinput(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:

TTestCase
TObject
#SetUp
#TearDown
+Check( test: Bool; msg: str)

TNumList TNumListTest
+ Nums: Array of real #mNumList
#SetUp
+ Count: integer #TearDown
+testAdd
+ Average: real +testGetNum
+ Total: real +testSetNum
+ Add( pNum: real ) +testTotal
+testAverage

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:

program TNumListTest;

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.

TNumList = class(TObject)
private
FCount: integer;
FNums: array [0..199] of real;
function GetCount: integer;
function GetAverage: real;
function GetTotal: real;
function GetNums(ind: Integer): real;
procedure SetNums(ind: Integer; const Value: real);
public
constructor Create;
procedure Add( pNum: real );
property Nums[ ind: Integer ]: real read GetNums write SetNums;
published
property Count: integer read GetCount;
property Total: real read GetTotal;
property Average: real read GetAverage;
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:

unit NumListTest;

interface
uses
TestFrameWork;

type

TNumListTest = class(TTestCase)
protected
procedure SetUp; override;
procedure TearDown; override;
published
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!
Whathappens 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?
Whathappens 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~

You might also like