Unit Testing With TestComplete - Example

Requirements
Files

Author: SmartBear Software
Applied to: TestComplete 10

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

Note: To perform unit testing, you must have the TestComplete Desktop module. It includes the required plug-ins and allows you to work with internal objects, methods and properties of the tested applications.

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 (to obtain this combo box, click Classic and select  "C#Script" from the Language drop-down list). 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 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.

Adding NameExtractor.exe as the tested application
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 do 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.

TCUnitTest editor
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):

Loading unit tests to TestComplete
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 Close method on the next page of the wizard.


      Picture 8 – The Process Action operation properties

      The complete keyword test will look like the following:

      The keyword test
      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:

NameExtractorUnitTest project’s test items
Picture 10 – NameExtractorUnitTest project’s test items

Now everything is ready for unit testing!

Testing Example

Now let’s see how it works.

  1. 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.

  2. 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:

    The Unit Testing Log

    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:

    The detailed test log
    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 | Project 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 Additional Information 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:

 The succeeded 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:

More Information