Crash Course in using CppUnit

Introduction

This document will introduce you to a testing framework called CppUnit. CppUnit is a C++ port of the JUnit testing framework developed by Erich Gamma and Kent Beck. This document describes the solaris port done by Jérôme Lacoste. The main purpose of CppUnit is to support developers in doing their unit testing of C++ programs. For students using the C++ language for the project, we expect you to use CppUnit extensively for your testing purposes. Recall that one of the required program quality attributes for your project is reliability. CppUnit can be used to help you achieve that.

This document can be considered a "port" of the JUnit Guide I wrote. In particular, this document talks about using CppUnit in Solaris e.g. in sun450 (although it should also work for other Unix variants such as Linux). As in the JUnit Guide, I have included only the bare minimum to get you started. I will first go through the installation of CppUnit in the next section, followed by a description of how to use CppUnit using a sample program. Next, I will suggest some ways of organizing your project and test codes before ending the document with pointers to some useful references.


Installing CppUnit

Installation of CppUnit can be broken down into the following steps:
  1. Download the solaris port of CppUnit and unpack the archive in a location of your choice. For the rest of this document, I will assume that you are saving and unpacking your archive at the directory /home/student/engpk:
    cd /home/student/engpk
    gunzip UnixCppUnit.tar.gz
    tar -xvf UnixCppUnit.tar
            
    The above three commands will create a new directory called UnixCppUnit in /home/student/engpk.
  2. Compile the CppUnit source files:
    cd /home/student/engpk/UnixCppUnit
    make
            
    After the compilation, you should have the following in /home/student/engpk/UnixCppUnit/lib:
    -rwx------   1 engpk    compsc   1248528 libCppUnit.so*
    -rwx------   1 engpk    compsc   1200508 libCppUnitRunner.so*
    -rwx------   1 engpk    compsc   1215800 libCppUnitTest.so*
            
    Note: If you're compiling on Linux, the compilation will fail. What you need to do is to modify the file Make.rules in the config subdirectory before executing the make command. Look for the line starting with ARFLAGS in the file Make.rules and change the -G option to -shared on the same line and the compilation will be successful.
  3. Copy the header files of CppUnit to the include directory:
    cd /home/student/engpk/UnixCppUnit/include
    cp ../test/framework/*.h .
    cp ../test/textui/*.h .
            
  4. Test your installation by running one of the sample programs provided:
    cd /home/student/engpk/UnixCppUnit/samples
            
    You will see a file called test which is an executable program. Unfortunately, "test" is also a builtin command of the bash shell which most of you are running when you logon to our systems. Therefore, you need to rename this file before running it:
    mv test testprog
    ./testprog ExampleTestCase
            
    When you run the program testprog, you will see an error message:
    ld.so.1: testprog: fatal: libCppUnit.so: open failed: No such file or \ 
    directory
    Killed
            
    This is because the linker is unable to find the CppUnit shared library. What you should do now is to set the LD_LIBRARY_PATH environment variable (enter the following as a single line):
    export LD_LIBRARY_PATH=
    $LD_LIBRARY_PATH:/home/student/engpk/UnixCppUnit/lib
            
    You may want to put the above line in your .bash_profile or .profile so that the library path is always set when you logon to one of the main servers. Now, try running the sample program again:
    ./testprog ExampleTestCase
            
    You will see some failure messages - it's okie. This is because the sample program creates a few failed tests:
    ..F
    .F
    
    !!!FAILURES!!!
    Test Results:
    Run:  3   Failures: 2   Errors: 0
    There were 2 failures: 
    1) line: 28 ExampleTestCase.cpp "result == 6.0"
    2) line: 50 ExampleTestCase.cpp "expected: 12 but was: 13"
            
    If you got the above message, then CppUnit has been installed properly. You are now ready to use CppUnit for your development work.

Using CppUnit

Overview

In this section, I will provide you with some general steps on how to go about using CppUnit. The next section illustrates the key steps using an example. My advice is to skim through this section quickly for an overview. Then, as you go through the example in the next section, refer back to this section frequently to get the whole picture.

Assuming that you want to test a class called Parser. The following are the general steps to use the CppUnit framework to test this class:

  1. Write a class (let's call it TestParser) to test the Parser class. This class must inherit the class TestCase which is defined by the CppUnit framework.
  2. Create a constructor for this class, passing a name that is representative of the set of tests for this class as the parameter.
  3. Create a fixture. A test fixture is a set of sample objects that you want to (re)use during testing. For example, you might create a few sample source files for the Parser to parse. CppUnit provides a setUp and a tearDown method to manage the fixture. Therefore, you can eg. create file objects in setUp to open the source files and release these resources in the tearDown method. The important thing to note is that setUp and tearDown will be called for every 'test' that you run.
  4. Each 'test' you perform is represented by the implementation of a method in the test class. For example, if you want to test whether the parser extracts the tokens correctly, you can implement a method called testGetToken. The collection of test methods you implement forms a test suite.
  5. In each test method you create, use the assertion mechanism provided by CppUnit to compare the results of running the test and the results you expected. This will enable you to create repeatable tests as well as saving you lots of time from visually inspecting the results.
  6. Finally, use the textual version of the TestRunner tool to run the tests and collect the results. There's also a graphical version but it hasn't been ported to solaris yet. So, you can't use it at the moment. As each test is run, CppUnit will provide feedback on whether the test ran successfully, or the test failed, or an exception has occurred.

Example

In this section, I will describe how you can use CppUnit using an example. Take a few minutes to examine the following two classes (.h and .cpp files) to see what they are doing: Basically, there are two classes: Course and Student. Each Course contains a name eg. CS3214s and an integer grade which ranges from 0 to 100. Each Student has a name, a number as well as a list of course grades. You can add the grade that a student scores at a particular course using the assignGrade method and retrieve the grade of a particular course using the getGrade method. Create a directory to store the above 4 source files. Let's call this new directory cs3214s:
cd /home/student/engpk
mkdir cs3214s
Now, compile the files as follows:
g++ -c Course.cpp
g++ -c Student.cpp
You will see two files - Course.o and Student.o created in the directory. Just leave them alone for the moment. Next, create the test class. The following are the test files I wrote for the Student class (called TestStudent.h and TestStudent.cpp): Notes for the preceding code: Now, compile your test programs:
g++ -c TestStudent.cpp

In file included from TestStudent.cpp:1:
TestStudent.h:7: TestCase.h: No such file or directory
TestStudent.h:8: TestSuite.h: No such file or directory
TestStudent.h:9: TestCaller.h: No such file or directory
TestStudent.h:10: TestRunner.h: No such file or directory
When you compile the test program, the above error message will appear. This is because the compiler is unable to locate the CppUnit header files. The correct way to compile this (assuming you store the CppUnit header files in /home/student/engpk/UnixCppUnit/include as I described earlier) is:
g++ -I/home/student/engpk/UnixCppUnit/include -c TestStudent.cpp
At the end of this step, you would have TestStudent.o. Now, put everything together to create an executable test program (execute the following as a single command):
g++ -o tester Course.o Student.o TestStudent.o TestRunner.o \
-lCppUnit -lCppUnitRunner
The above command will create the executable called tester.
Note: If the compiler complains that it can't find -lCppUnit (which might happen for Linux systems), use the -L option to include the path containing the shared library and compile as follows:
g++ -L/home/student/engpk/UnixCppUnit/lib -o tester Course.o Student.o \
TestStudent.o TestRunner.o -lCppUnit -lCppUnitRunner
To run the test suite, simply type:
tester TestStudent
and you're off doing your unit testing :-)

Exercise

The best way to learn CppUnit is to use it. So, here's a small exercise you can do to get some hands-on practice. Let's say we now extend the Student class by adding a method to find the average grade of all the courses taken by the student. You can add the following piece of code to Student.h and Student.cpp:
// In Student.h under public
// Method to return the average grade
float findAveGrade();

// In Student.cpp
// Method to return the average grade
float Student::findAveGrade() {
  float sum = 0.0, average;

  // sum up the marks in all the courses
  for (int i = 0; i < no_of_courses; i++)
    sum += course_grades[i].getCourseGrade();
  average = sum / no_of_courses;
  return(average);
}
Your job is to write a method in the TestStudent class to test this newly created method. Give it a try and see whether you really know how to use CppUnit ;-)

Organizing the project code

As you go through each iteration of your project, you will find the amount of source code increasing. If you do not organize properly, you will soon end up with source files lying all over the directory, making it difficult to find the right file(s) or coordinate code written by different members. One straightforward way of organizing your code is to divide different member's code into separate directories. For example, you might create the following subdirectories to store the code of different parts of the SPA: In addition, you should also create an "include" subdirectory to store all those .h files that other members will need and a "lib" directory containing all the necessary .o files. Remember, there should never be a need to look at other member's .cpp files. You all should only communicate via the interfaces you define, which are materialized as header (.h) files during implementation.

You should also learn how to make use of the utility make. Use make to do automatic compilation and integration of the code from various members. The process is non-trival and requires a lot of coordination between members. Hence, it is important that each team tie down among themselves a standard way of managing their own source code.

Organizing the test code

Following the guideline of "Code a little, test a little, code a little, test a little,...", you'll soon find the amount of test code increasing rapidly as the project progresses. Again, you can use the make utility to assist you. You can either place the test code in the same directory or as a subdirectory of the directory containing the set of code you are testing. The important thing is not to sprinkle your test code all over the place - it will reduce the efficiency of your testing process.

References

Unfortunately, there isn't a lot of documentation regarding CppUnit. Instead, you should read up those references in the JUnit Guide to understand the overall framework and apply it in the context of CppUnit. Remember, to maximize the potential of CppUnit, you need to read more and experiment on your own. If you need to, reading the CppUnit's source will also be helpful.