Author: AutomatedQA Corp.
Last updated: April 16, 2009
Applied to: TestComplete 7
This article is written on the base of Eric Holton’s article "Unit Testing With TestComplete"
published in 2002
Preface
The main idea of unit testing is testing software with a small piece of source code
(unit, component, and/or function) of the same software. "Unit testing" means that
the software consists of "units" which are separate testable parts of the product.
An individual program, class, method, function etc. can be such "unit". Unit testing
allows checking whether a unit behaves as the developer intended and whether a unit
corresponds to the design specifications. Unit testing provides an ability of independent
testing for each software unit.
Source code for unit testing is created by the developer as a part of software.
To illustrate how to implement unit testing and automate testing process with TestComplete,
we will build a small project. At each stage of the project development we will
write tests to check a particular project unit’s design and functionality. These
tests will be implemented in the UnitTestDriver class. Then we will create a TestComplete
project and configure it for unit testing. After that TestComplete will be able
to provide automated unit testing of the project. So, our unit testing is based
on the following main features:
- The tested object is implemented as a class and included in the C# project
- Testing methods are the methods of testing class included in the C# project
- The TestComplete project loads testing methods, executes them and posts messages
to the test log, if any exceptions rose.
You can download the tested application and TestComplete project using the links
from the box on the right.
The step-by-step explanation is given in the following sections:
- Object Description and Business Rules – the object description
- Creating the C# Project – the project creation, adding required references
- Setting up TestComplete - unit testing automation with TestComplete
- Testing Sample – a short sample how to use TestComplete for executing tests and
tracing errors
Object Description and Business Rules
For instance, there is an object that we will implement in our application:
- Name Extractor
The Name Extractor takes the text input of an English/American proper name (i.e.
"Mr. John Smith") and extracts the following information from it:
- Title (i.e. "Mr")
- First Name (i.e. "John")
- Middle Name (blank in sample case)
- Last Name (i.e. "Smith")
- Suffix (blank in sample case, could be Jr, II, PhD, DDS, etc)
From this one input (proper name), we have generated five outputs. In order to get
these outputs, we should define some business rules.
- Business Rules
- The name could be broken in to a word list.
- The words have the strict order – the Title, the First Name, the Middle Name, the
Last Name and the Suffix.
- A word can contain a hyphen.
- If the name contains only one word, it is treated as the Last Name.
- If the name contains two words and the first word is a title, the second word is
treated as the Last Name.
- If the name contains two or more words and the first word is not a title, the first
two words are treated as the First Name and the Last Name.
- If the word is in the title list, it is treated as the title. If not the Title remains
empty.
- If the word is in the suffix list, it is treated as the suffix. If not the Suffix
remains empty.
The NameExtractor tested object will correspond to the NameExtractor tested class
in our project. Then we will implement unit tests for the object in the UnitTestDriver
class.
Creating the C# Project
We will build a C# project in Microsoft Visual Studio 2005 in our example. Then
we will work with the following project’s files:
- NameExtractor.cs – the object implementation, tested class
- UnitTestDriver.cs – testing class
- UnitTestForm.cs – the application main form
- UnitTestNameExtractor.cs – the main program
- Suff__Title_Lists.resx – the resource file containing acceptable titles and suffixes
So, start Visual Studio and create a new Windows Application C# project in
it. Name the project "UnitTestNameExtractor". Modify the project according to the
following steps:
1. Add the NameExtractor Class
The tested object will be implemented in the NameExtractor class. First of all we
should launch Visual Studio and create a new Visual C# | Windows Application
project in it.
Now we can add new classes to the created project. To add a class to the project,
open the Solution Explorer panel, right-click the created project and select Add
| Class... from the context menu. Then create a new class named "NameExtractor.cs".
In the "NameExtractor.cs" file add the following code for the new NameExtractor
class:
namespace NameExtractor
{
class NameExtractor
{
//constructor
public NameExtractor()
{
}
# region public properties
public String FullName
{
get { return mFullName; }
set { mFullName = value; }
}
//read-only properties
public String Title
{
get { return mTitle; }
}
public String FirstName
{
get { return mFirstName; }
}
public String MiddleName
{
get { return mMiddleName; }
}
public String LastName
{
get { return mLastName; }
}
public String Suffix
{
get { return mSuffix; }
}
#endregion
//private members
private String mFullName;//full name
private String mTitle;
private String mFirstName;
private String mMiddleName;
private String mLastName;
private String mSuffix;
private String[] mWords; //array of the words obtained from the mFullName
private Int32 mNumWords; //the number of words in the mFullName
private void SetFullName(String Value)
{
}
//protected members
protected void ExtractWords(String Value)
{
}
protected void ParseName()
{
}
protected Int32 FindTitle()
{
}
protected Int32 FindSuffix()
{
}
protected Int32 FindFirstName()
{
}
protected Int32 FindMiddleName()
{
}
protected Int32 FindLastName()
{
}
class ENameExtractorError : Exception
{
public ENameExtractorError(String Message)
{
}
}
}
Now we will step by step fill the declared methods according to the business rules.
First of all we can divide the given string into separate words with the System.String.Split
method. To extract words from a string to an array, we should specify a set of separators
for this string. It is the Separators array in the following code. Separated
words are saved to an array of strings, words. In addition, we exclude empty
strings and copy the needed words to another array, words1.
protected void ExtractWords(String Value)
{
Char[] separators = { ' ', ',', '.', ':', '\t' };
String[] words;
//use the System.String.Split method
//to divide an input string into separate words
words = Value.Split(separators);
String[] words1 = new String[5];
//exclude empty words (if any)
int i = 0, k = 0;
while ((i <= words.Length - 1) && (k <= 4))
{
{
if (words[i] == "") { i++; continue; };
words1[k] = words[i];
mNumWords = k+1;
k++;
i++;
}
}
//now the words1 array contains no more than 5 words
mWords = words1;
}
Then we should determine which of these words is Title, First Name and so on. As
you can see, it is easy to start words determination with finding the title and
suffix in the full name. To find a suffix, add the following code:
protected Int32 FindTitle()
{
//0 - the title might be the first word only
if (mWords != null)
{
if (Suff__Title_Lists.TitleList.Contains(mWords[0]))
{
mTitle = mWords[0];
return 0;
}
else
{
return -1;//no title
};
}
return -1;
}
The returned value is equal to -1 if the title is not found in the full name. Then
find the suffix in the same way:
protected Int32 FindSuffix()
{
if (mWords[4] != null)
{
mSuffix = mWords[4];
return 0;
}
else
{
if ((mWords[2] != null) && (Suff__Title_Lists.SuffixList.Contains(mWords[2])))
{
mSuffix = mWords[2];
return 0;
}
if ((mWords[3] != null) && (Suff__Title_Lists.SuffixList.Contains(mWords[3])))
{
mSuffix = mWords[3];
return 0;
}
}
return -1;
}
Now we know the title and suffix (if any) and it is easy to determine other words,
Last Name, First Name and Middle Name. You can find the full source code in the
example.
After all find methods are ready, we can create the complete method to parse an
input string:
protected void ParseName()
{
//initial values
mTitle = "";
mFirstName = "";
mMiddleName = "";
mLastName = "";
mSuffix = "";
//finding words
if ((mFullName != null) && (mFullName != ""))
{
ExtractWords(mFullName);
FindTitle();
FindSuffix();
FindLastName();
FindFirstName();
FindMiddleName();
}
}
Now all required methods are implemented and our class is ready to be tested. As
we chose the unit testing technology, we should implement testing methods in a particular
class of the C# project.
2. Add the UnitTestDriver Class
We will implement tests for NameExtractor as the UnitTestDriver class’ methods.
Before creating tests we will define some helper functions to make our code more
convenient and easier. So, create the UnitTestDriver.cs file and add the following
code to the class definition:
class UnitTestDriver
{
//an object of tested class NameExtractor
public NameExtractor NameExtractor1;
public UnitTestDriver()
{
NameExtractor1 = new NameExtractor();
}
#region //helper functions controlling test results
private String NotEqualsErrorMessage(String Expected, String Actual, ref String Msg)
{
if (Msg != "")
{
Msg =Msg+" : expected "+Expected + ", but was: " + Actual;
}
return Msg;
}
private String EqualsErrorMessage(String Expected, String Actual, ref String Msg)
{
if (Msg != "")
{
Msg = Msg + " : expected " + Expected + " and actual were: " + Actual;
}
return Msg;
}
private void CheckEquals(String Expected, String Actual, String Msg)
{
if (Expected != Actual)
{
NotEqualsErrorMessage(Expected, Actual, ref Msg);
Exception exception = new Exception(Msg);
throw exception;
}
}
private void CheckNotEquals(String Expected, String Actual, String Msg)
{
if (Expected == Actual)
{
NotEqualsErrorMessage(Expected, Actual, ref Msg);
Exception exception = new Exception(Msg);
throw exception;
}
}
# endregion
}
Now we can add tests for our object. We have 8 business rules, so we need at least
8 independent tests, one test for each business rule. To do this, add the following
code to the UnitTestDriver.cs file:
//tests
//Busuness Rule 1
public void Test1()
{
}
//Busuness Rule 2
public void Test2()
{
}
//Busuness Rule 3
public void Test3()
{
}
//Busuness Rule 4
public void Test4()
{
}
//Busuness Rule 5
public void Test5()
{
}
//Busuness Rule 6
public void Test6()
{
}
//Busuness Rule 7
public void Test7()
{
}
//Busuness Rule 8
public void Test8()
{
}
We will implement tests in our work later. You can also add new tests and modify
them at any time of the project development.
3. Prepare the C# Project for Unit Testing
After adding the NameExtractor class, we should set up our .NET project to make
the testing class available to be called via TestComplete tests:
- Add the AutomatedQA.TestComplete.UnitTesting.dll assembly to the References
list of the project. By default, this assembly is in the
<TestComplete>\Bin\Extensions
folder. The assembly holds classes that are necessary to call testing functions
via visually configured tests. - Set the Copy Local property of this assembly to True.
- To make a testing class available to be called via visually configured tests, you
should call the AddClasses method of the UnitTesting object. (This object is declared
in the AutomatedQA.TestComplete.UnitTesting assembly).
According to the described actions we will add the following code to the UnitTestForm.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace NameExtractor
{
public partial class UnitTestForm : Form
{
public UnitTestForm()
{
//registering tested and testing classes
Type[] typearr = { typeof(UnitTestForm), typeof(NameExtractor), typeof(UnitTestDriver) };
TestComplete.UnitTesting.AddClasses(typearr);
InitializeComponent();
}
}
}
You can register any number of classes by specifying them in the typearr
array. For more information about how to prepare applications for unit testing,
see the "Unit Testing" in the TestComplete help.
4. Add Resources
According to Business Rules 7 and 8 we know all acceptable titles and suffixes.
So it is suitable to store them within a particular resource file. For that purpose
we will add the Suff__Title_Lists.resx resource file to the project and specify
two strings in it:
SuffixList = "DDS,CFA,CEO,CFO,Esq,CPA,MBA,PhD,MD,DC,Sr,Jr,II,III,IV"
TitleList = "Mr.,Mr,Ms.,Ms,Miss.,Miss,Dr.,Dr,Mrs.,Mrs,Fr.,Capt.,Lt.,Gen.,President,Sister,Father,Brother,Major"
Then, for instance, the TitleList resource item will be available from our code:
Suff__Title_Lists.TitleList
Thereby, we created a skeleton for our NameExtractor class and future unit tests,
prepared the project for unit testing, but the methods are so far empty. Now we
will set up TestComplete to be able to automate the whole unit testing process.
Setting up TestComplete
To automate unit testing of our project, we will use the Unit Testing project item
in TestComplete. You can execute tests created under various testing frameworks
– Nunit, MSTest, JUnit and DUnit. Instead of running these tests individually, you
can integrate them into a TestComplete project that tests the same application and
runs all of these tests together. If you are not using any external testing tools,
you can use TestComplete’s TCUnitTest for unit testing of your application. In this
case TestComplete performs testing by itself and no other tools or frameworks are
needed. We will use this approach.
1. Creating Project
First of all create a new project suite. To do this, start TestComplete, select
File | New | New Project Suite… from the main menu. After the dialog appears,
type the project suite name "NameExtractor", select the suite location and click
OK.
Then add a new project to the suite. Right-click the NameExtractor project suite
in the Project Explorer panel. Select Add | New Item… from the context
menu. After the dialog appears, type the project name "NameExtractorUnitTest" and
choose the preferred scripting language on which you will be able to write script
routines, if needed. We won’t use scripts in our example, but specify "C#Script"
in the Language combo box for future. Click OK to confirm settings
and to add the project to the suite.
2. Adding Tested Application
Then add the TestedApps project item. To do this, right-click the NameExtractorUnitTest
project in the Project Explorer panel. Select the Add | New Item…
from the context menu. After the dialog appears, find and select the Win32 Tested
Applications item from the list and then click OK to confirm addition.
Now we should add our NameExtractor.exe as the tested application to the project.
Right-click the TestedApps project node and select Add | New Item…
from the context menu. After the dialog appears, find the NameExtractor.exe and
double-click on it. The Workspace panel will look like it is shown in Picture 1.
Picture 1 – Adding NameExtractor.exe as the tested application
Now we can automate running and stopping our application from TestComplete keyword
tests and scripts.
3. Loading Unit Tests to TestComplete
Our testing methods are methods of the UnitTestDriver class, so we should provide
access to them for TestComplete. We should also "inform" TestComplete that our methods
are intended for unit testing.
To to this, add the UnitTesting project item to the project. Right-click the NameExtractorUnitTest
project in the Project Explorer panel. Select Add | New Item… from
the context menu. After the dialog appears, find and click the Unit Testing item
in the list, type its name, for instance, "UnitTesting1" in the Name edit
box and click OK to confirm addition.
Then we will add unit tests from UnitTestNameExtractor C# project. Right-click the
UnitTesting1 project item and select Add | New Item… from the context menu.
Select "TCUnitTest" in the Select Project Item dialog and type its name,
for instance, TCUnitTest1. Click OK to confirm addition.
Find and launch the NameExtractor.exe application. The NameExtractor process will
appear in the system (you can explore processes in Windows Task Manager).
Then click the ellipsis button in the TCUnitTest editor. Select the NameExtractor
process from the list and click OK. The editor will look like it is shown
in Picture 2.
Picture 2 – TCUnitTest editor
Select the Run Selected Test Only in the Mode section. Then click
Load to get the list of available methods from the NameExtractor process.
We do not need methods of NameExtractor class to use them from TestComplete, so
we uncheck the NameExtractor group. After that the TCUnitTest editor will look like
the following (see Picture 3):
Picture 3 – Loading unit tests to TestComplete
Select File | Save All from the main menu to save made changes.
4. Automating Testing Process
Now we should create a short keyword test to automate running and stopping the tested
application. Note that you can also perform these actions in scripts, but we demonstrate
how to do this from keyword-driven tests:
- Create a new keyword test, named "Test1".
- Add the Log Message operation to the test:
- Select the Log Message operation from the Operations panel.
- Drag the operation to the test.
- In the ensuring dialog specify the message to be added to the test log:
"Setup
Tests. Preparing for unit testing..." and click Finish.
Picture 4 – Specifying the Log Message
- To be able to run unit tests implemented in our C# project, the NameExtractor.exe
application must be run in the system. So we will run the application and then check
whether the NameExtractor process appears in the system.
To run the application, add the Run TestedApp operation to the test and select NameExtractor
to be run. Set the Timeout parameter equal to 3000:
Picture 5 – Setting up the timeout for the Run TestedApp operation
- Then, to check whether the process already exists in the system, add the If… Then
operation to the test. Select the Code Expression mode in the Value 1 column and
type
"Sys.Process("NameExtractor").Exists" in the Value column. Set
the Condition to "equal to" and specify the False boolean constant in the Value
2 band. The Exists method returns False, if there is no specified process in the
system. Then add the Stop Execution operation as the previous operation’s child.
Check the "Stop current test only" in the operation properties. You
can also specify a message to be posted to the log by the Stop Execution operation:
Picture 6 – Specifying the message for the Stop Execution operation
- Now we will add our unit testing methods to be executed by TestComplete. As you
remember, we loaded these methods within the TCUnitTest1 project item. So, add the
Run Test operation to the keyword test and select TCUnitTest1 in the operation properties:
Picture 7 – Specifying the unit test for the Run Test operation
- After all tests finished, we will close the tested application. To do this, add
the Process Action operation to the test and type the name of process, "NameExtractor"
as it shown in the Picture 8. Selecting from the list, you can specify an existing
process. Then click Next and select the Closemethod in the next page of the
wizard.
Picture 8 – The Process Action operation properties
The complete keyword test will look like the following:
Picture 9 – The keyword test
After the keyword test’s creation, we should configure our TestComplete project
and specify the main test item for it. To do this, right-click the NameExtractorUnitTest
project in the Project Explorer and select Edit | Test Items from
the context menu. In the Test Items page add a new item and specify the Test1
keyword test in it. The page will look like it is shown in the following picture:
Picture 10 – NameExtractorUnitTest project’s test items
Now everything is ready for unit testing!
Testing Example
Now let’s see how it works.
- Type the following code in the UnitTestDriver.Test2 method (it is located in the
UnitTestDriver.cs file):
public void Test2()
{
NameExtractor1.FullName = "John Brown";
CheckEquals("", NameExtractor1.Title, " Title is not correct");
CheckEquals("John", NameExtractor1.FirstName, " First Name is not correct");
CheckEquals("", NameExtractor1.MiddleName, " Middle Name is not correct");
CheckEquals("Brown", NameExtractor1.LastName, " Last Name is not correct");
CheckEquals("", NameExtractor1.Suffix, " Suffix is not correct");
NameExtractor1.FullName = "Mr. John Brown";
CheckEquals("Mr", NameExtractor1.Title, " Title is not correct");
CheckEquals("John", NameExtractor1.FirstName, " First Name is not correct");
CheckEquals("", NameExtractor1.MiddleName, " Middle Name is not correct");
CheckEquals("Brown", NameExtractor1.LastName, " Last Name is not correct");
CheckEquals("", NameExtractor1.Suffix, " Suffix is not correct");
NameExtractor1.FullName = "John Brown. Mr";
CheckEquals("John", NameExtractor1.FirstName, " First Name is not correct");
CheckEquals("", NameExtractor1.MiddleName, " Middle Name is not correct");
CheckEquals("Brown", NameExtractor1.LastName, " Last Name is not correct");
}
We will leave other tests (testing methods, Test1, Test3 and so on) empty for this
time. Then rebuild the UnitTestNameExtractor project so that the executable appears
in the /bin/debug folder.
- Then, turn to TestComplete. If you have prepared the NameExtractorUnitTest
project as it is described above, right-click the project and select Run NameExtractorUnitTest
[Project] from the context menu. After that the test items specified on
the Test Items page of the project properties will be executed. As you know,
we have the only one test item, the Test1 keyword test. After the test is finished,
the Test Log appears:
Picture 11 – The Unit Testing Log
As you can see, an error has occurred during the unit testing. To determine the
failed unit test, click the Details link. After that the detailed log information
will appear:
Picture 12 – The detailed test log
Note that TestComplete executed all tests and did not stop testing after the first
failed test – UnitTestDriver.Test2. You can set up the needed behavior of TestComplete
in the Playback section of the default project properties (select Tools |
Default Project Properties… from the main menu).
Now return to the failed test. You can see the additional information about the
error – the exception type, method’s names where it has occurred - in the Remarks
panel under the Test Log. This information simplifies finding the problem
in the source code. Open the UnitTestDriver.cs and find:
NameExtractor1.FullName = "John Brown, Mr";
CheckEquals("John", NameExtractor1.FirstName, " First Name is not correct");
CheckEquals("", NameExtractor1.MiddleName, " Middle Name is not correct");
CheckEquals("Brown", NameExtractor1.LastName, " Last Name is not correct");
Of course the "John Brown, Mr" input string is not suitable for Business Rule 2
and the "Mr" word was incorrectly recognized as the Last Name. That’s why the "Brown"
became the Middle Name and then this fact caused the exception. So replace that
code with the following:
NameExtractor1.FullName = "John Brown, PhD";
CheckEquals("John", NameExtractor1.FirstName, " First Name is not correct");
CheckEquals("Brown", NameExtractor1.LastName, " Last Name is not correct");
CheckEquals("PhD", NameExtractor1.Suffix, " Suffix is not correct");
Now let’s check that we have fixed the error. Save all changes made to the UnitTestNameExtractor
C# project and rebuild the solution. Then open TestComplete and run the UnitTestNameExtractor
project. After the tests finish, you will see the following detailed test log:
Picture 13 – The succeeded test log
Thereby, we can use the unit testing of NameExtractor to make sure that our object
corresponds to given business rules. The only thing to do is to implement Test1,
Test3, and other methods. So, the automated unit testing process includes:
- The direct object development
- The testing methods’ development
- The TestComplete project’s configuration
You can download the full source code from this example and the TestComplete project
(see the link in the beginning of the article).
Wrapping Up
Unit testing is very good at forcing the developer to work on debugging code while
the code is still fresh in his/her mind. This paper shows one way of using TestComplete
in testing.
One of the most important things to do with unit testing is:
If a bug is discovered in later testing, write a test that will uncover the bug in
the unit testing. Then fix the bug.
References:
Dave Thomas and Andy Hunt, "Learning to Love Unit Testing", in The Software Testing
& Quality Engineering Magazine, January/February 2002, volume 4, issue 1. Pp.32-38.
Martin Fowler, ed., Refactoring: Improving the Design of Existing Code, Addison
Wesley Longman, 1999; ISBN 0201485672.
Kent Beck, extreme Programming explained: Embrace Change, Addison Wesley Longman,
2000; ISBN 0201616416
Mark Fewster and Dorthy Graham, Software Test Automation: Effective use of test
execution tools, Addison Wesley, 1999, ISBN 0201331403
Edward Kit, Software Testing in the Real World: improving the process, Addison Wesley,
1995, ISBN
0201877562
Elfriede Dustin, Jeff Rashke and John Paul, Automated Software Testing: Introduction,
Management and Performance, Addison Wesley, 1999, ISBN 0201432870
Robert V. Binder, Testing Object-Oriented Systems: Models, Patterns and Tools, Addison
Wesley, 2000, ISBN 0201809389
Watts S. Humphrey, A Discipline for Software Engineering, Addison Wesley, 1995,
ISBN 0201546108
Steve McConnell, Code Complete, Microsoft Press, 1993, ISBN 1556154844
Steve Maguire, Writing Solid Code, Microsoft Press, 1993, ISBN 1556155514
Websites:
http://www.stqemagazine.com
http://www.stickyminds.com
http://www.qaforums.com
http://dunit.sourceforge.net
http://www.xprogramming.com