Unit-tests with C++ using the framework CppUnit

Besides JUnit, the best-known and probably most-used representative of the frameworks for unit testing, there are implementations for many different programming languages. CppUnit is such a framework for programming software tests after the principle of the unit tests for the programming language C++. It is a port of JUnit to C++ which runs on a row of unix derivatives, as well as under Windows. Visual C++ (from 6.0 on) and Borland C++ are supported by this framework and, in addition to that, different Unix compilers like the GNU C++ compiler. Only the framework and one of the mentioned compilers as well as a Unix/Linux or Windows operating-system are needed. You'll find closer and rather general information on the subject of unit testing on the page Unit tests. The following sections of this tutorial will demonstrate the usage of the framework CppUnit.

Unit-testing at the example of the class fraction

In the following I would like to provide an easy introduction using the example of the class fraction which implements some basic operations with fractions. The class fraction would be given and it is able to reduce fractions, to add and to subtract fractions with each other. In addition, it contains a constructor, an assignment operator and some comparison operators. The functions gcf (calculation of the greatest common factor) and lcd (calculation of the least common denominator) are merely auxiliary functions and actually do not belong here. The following code block contains a possible definition of the class fraction.

// CppUnit-Tutorial
// file: Fraction.h
#ifndef FRACTION_H
#define FRACTION_H

#include <iostream>

using namespace std;

// some helper functions (actually don't belong here)
// calculates the greatest common factor (for reducing)
unsigned int gcf (unsigned int, unsigned int);
// calculates the least common denominator (for expanding)
unsigned int lcd (unsigned int, unsigned int);

// definition of an exception-class
class DivisionByZeroException
{
};

// simple definition of a fraction-class
class Fraction
{
    public:
        // constructor
        Fraction (int = 0, int = 1) throw (DivisionByZeroException);

        // copy-constructor and assignment-operator
        Fraction (const Fraction&);
        Fraction& operator= (const Fraction&);

        // comparing operators
        bool operator== (const Fraction&) const;
        bool operator!= (const Fraction&) const;

        // arithmetic operators
        friend Fraction operator+ (const Fraction&, const Fraction&);
        friend Fraction operator- (const Fraction&, const Fraction&);

        // output on stdout
        friend ostream& operator<< (ostream&, const Fraction&);

    private:
        // method for reducing
        void reduce (void);

        // variables for saving the numerator and denominator
        int numerator, denominator;
};

#endif

Now, with the help of a testclass, it should be checked whether the addition and subtraction-operations work correctly. In addition (with the assistance of the CppUnit-framework) it should be checked whether an exception with the name DivisionByZeroException will be thrown, when generating an invalid fraction (if the denominator is zero). The correctness of the method reduce, which is executed after every operation which changes the numerator or the denominator of the fraction, is tested indirectly with the comparison operators.

Testclass

Now it goes to the definition and implementation of the testclass which should do the demanded tests. The classes of the CppUnit-framework are placed in a namespace which is defined by the macro CPPUNIT_NS. Now the testclass is derived from the class TestFixture which is dropped in this namespace. A TestFixture is a Wrapper class which serves to create certain basic conditions which are needed for a series of tests. Thus, for example, instances of objects which are later needed by the single tests can be generated and initialized. For this purpose the methods setUp and tearDown can execute these preparations or do the finishing.

The CppUnit-framework defines a set of macros with whose help some tiresome, as well as fault-prone steps can be done. These macros are defined in the header-file cppunit/extensions/HelperMacros.h which must be integrated, therefore. Among them are macros for initiating and finishing a test suite (CPPUNIT_TEST_SUITE and CPPUNIT_TEST_SUITE_END). A test suite is a collection of tests (test line) which should be carried out - mostly with instances of the class to be tested. While initiating a test suite the type (name) of the testclass which contains the tests to be executed must always be passed. In this case this is the class fractiontest.

In addition, a macro with whose help one can integrate tests into the test suite, is defined. In the example this CppUnit macro with the name CPPUNIT_TEST is used. As a parameter it needs the name of the test method to be performed. The test which is implemented in the suitable method is thereby registered in the test suite.

// CppUnit-Tutorial
// file: fractiontest.h
#ifndef FRACTIONTEST_H
#define FRACTIONTEST_H

#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include "Fraction.h"

using namespace std;

class fractiontest : public CPPUNIT_NS :: TestFixture
{
    CPPUNIT_TEST_SUITE (fractiontest);
    CPPUNIT_TEST (addTest);
    CPPUNIT_TEST (subTest);
    CPPUNIT_TEST (exceptionTest);
    CPPUNIT_TEST (equalTest);
    CPPUNIT_TEST_SUITE_END ();

    public:
        void setUp (void);
        void tearDown (void);

    protected:
        void addTest (void);
        void subTest (void);
        void exceptionTest (void);
        void equalTest (void);

    private:
        Fraction *a, *b, *c, *d, *e, *f, *g, *h;
};

#endif

As already described the methods setUp and tearDown do the preparation and finishing of the tests. They are executed automatically within the CppUnit-framework and thus prepare the basic conditions. In the example the fraction-instances (a to h) are generated in these methods or are deleted again after the tests. The protected area of the class contains the definition of the individual test methods. In their implementation these methods contain the actual tests which should be executed.

Now after the definition of the testclass it has to be implemented. The CppUnit macro CPPUNIT_TEST_SUITE_REGISTRATION makes sure that the test suite defined in the testclass fractiontest is entered on a global test list, the so-called registry.

// CppUnit-Tutorial
// file: fractiontest.cc
#include "fractiontest.h"

CPPUNIT_TEST_SUITE_REGISTRATION (fractiontest);

void fractiontest :: setUp (void)
{
    // set up test environment (initializing objects)
    a = new Fraction (1, 2);
    b = new Fraction (2, 3);
    c = new Fraction (2, 6);
    d = new Fraction (-5, 2);
    e = new Fraction (5, -2);
    f = new Fraction (-5, -2);
    g = new Fraction (5, 2);
    h = new Fraction ();
}

void fractiontest :: tearDown (void)
{
    // finally delete objects
    delete a; delete b; delete c; delete d;
    delete e; delete f; delete g; delete h;
}

void fractiontest :: addTest (void)
{
    // check subtraction results
    CPPUNIT_ASSERT_EQUAL (*a + *b, Fraction (7, 6));
    CPPUNIT_ASSERT_EQUAL (*b + *c, Fraction (1));
    CPPUNIT_ASSERT_EQUAL (*d + *e, Fraction (-5));
    CPPUNIT_ASSERT_EQUAL (*e + *f, Fraction (0));
    CPPUNIT_ASSERT_EQUAL (*h + *c, Fraction (2, 6));
    CPPUNIT_ASSERT_EQUAL (*a + *b + *c + *d + *e + *f + *g + *h, Fraction (3, 2));
}

void fractiontest :: subTest (void)
{
    // check addition results
    CPPUNIT_ASSERT_EQUAL (*a - *b, Fraction (-1, 6));
    CPPUNIT_ASSERT_EQUAL (*b - *c, Fraction (1, 3));
    CPPUNIT_ASSERT_EQUAL (*b - *c, Fraction (2, 6));
    CPPUNIT_ASSERT_EQUAL (*d - *e, Fraction (0));
    CPPUNIT_ASSERT_EQUAL (*d - *e - *f - *g - *h, Fraction (-5));
}

void fractiontest :: exceptionTest (void)
{
    // an exception has to be thrown here
    CPPUNIT_ASSERT_THROW (Fraction (1, 0), DivisionByZeroException);
}

void fractiontest :: equalTest (void)
{
    // test successful, if true is returned
    CPPUNIT_ASSERT (*d == *e);
    CPPUNIT_ASSERT (Fraction (1) == Fraction (2, 2));
    CPPUNIT_ASSERT (Fraction (1) != Fraction (1, 2));
    // both must have equal valued
    CPPUNIT_ASSERT_EQUAL (*f, *g);
    CPPUNIT_ASSERT_EQUAL (*h, Fraction (0));
    CPPUNIT_ASSERT_EQUAL (*h, Fraction (0, 1));
}

Now the already registered test methods can be implemented. The actual tests become defined with the help of the CppUnit macros CPPUNIT_ASSERT_EQUAL, CPPUNIT_ASSERT and CPPUNIT_ASSERT_THROW. These macros automatically insert the necessary source code needed for the execution of the tests in the test methods. Thus the CppUnit macro CPPUNIT_ASSERT_EQUAL checks whether the first parameter is like the second one. In case of the CppUnit macro CPPUNIT_ASSERT it is checked whether the passed expression returns the value True. On the other hand the CppUnit macro CPPUNIT_ASSERT_THROW is used to check whether the passed expression throws an exception of the passed type.

Testprogram

The next step is the implementation of the test program. First we need an instance of the class TestResult which acts as an event manager. This class serves to inform different listeners, which for instance collect the test results for a later output, about the test results and the test progress (events). The listener used here - an instance of the class TestResultCollector - will be informed about the test process, after he is added to the event manager.

Then all registered tests from the registry are added to the TestRunner which will execute these. Besides the event manager (TestResult) is used to inform the listener about the test process. The CompilerOutputter serves to display the test results in a format which resembles that of a compiler. That means that every error is displayed with line number and filename. In this manner the sources of error can be clicked on out of an IDE immediately to open the source code at the point at which the error has appeared.

At the end the program is terminated and returns the exit-code 0 if all tests ran positively. If any errors appeared while executing the tests, the exit-code 1 will be returned. Thereby it is possible to insert test programs into the built-process, because in the case of the appearance of test errors the built-process also stops with an error. The complete test program would look like this:

// CppUnit-Tutorial
// file: ftest.cc
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>
#include <cppunit/BriefTestProgressListener.h>

int main (int argc, char* argv[])
{
    // informs test-listener about testresults
    CPPUNIT_NS :: TestResult testresult;

    // register listener for collecting the test-results
    CPPUNIT_NS :: TestResultCollector collectedresults;
    testresult.addListener (&collectedresults);

    // register listener for per-test progress output
    CPPUNIT_NS :: BriefTestProgressListener progress;
    testresult.addListener (&progress);

    // insert test-suite at test-runner by registry
    CPPUNIT_NS :: TestRunner testrunner;
    testrunner.addTest (CPPUNIT_NS :: TestFactoryRegistry :: getRegistry ().makeTest ());
    testrunner.run (testresult);

    // output results in compiler-format
    CPPUNIT_NS :: CompilerOutputter compileroutputter (&collectedresults, std::cerr);
    compileroutputter.write ();

    // return 0 if tests were successful
    return collectedresults.wasSuccessful () ? 0 : 1;
}

Compiling

At the end it goes to the production of a makefile for the compilation of the test program and I proceed on the assumption that the destination system is a Unix/Linux system with an installed GNU make. For the sake of simplicity I will do it without the absolutely convenient use of autoconf and automake and use the following makefile here. The following section shows the contents of the file with the name makefile.

# CppUnit-Tutorial
# file: makefile
# next line has to be changed to the installation-path of CppUnit
CPPUNIT_PATH=/opt/cppunit

ftest: ftest.o fractiontest.o Fraction.o
    gcc -o ftest ftest.o fractiontest.o Fraction.o -L${CPPUNIT_PATH}/lib -lstdc++ -lcppunit -ldl

Fraction.o: Fraction.cc Fraction.h
    gcc -c Fraction.cc

fractiontest.o: fractiontest.cc
    gcc -c fractiontest.cc -I${CPPUNIT_PATH}/include

ftest.o: ftest.cc
    gcc -c ftest.cc -I${CPPUNIT_PATH}/include

clean:
    rm -f *.o ftest

Only the path in the first line of the makefile which leads to the installation place of the CppUnit-framework has to be customized. At the end a make at the command-line (in the same directory in which all the other files are placed) will translate the test program.

And off it goes ...

Now the test program can be started by the input of ./btest at the command-line according to which the following output should be generated.

amue@rechenknecht # ./ftest
fractiontest::addTest : OK
fractiontest::subTest : OK
fractiontest::exceptionTest : OK
fractiontest::equalTest : OK
OK (4)
amue@rechenknecht #

Because all four tests ran out without an error the implementation of the class fraction is correct.

CppUnit-Tutorial: Download

Whoever would like to try out this example even once, can download the sources for this example. I hope that I could give a short entrance with my descriptions about the usage of the CppUnit-framework and that this CppUnit-tutorial has fulfilled its purpose. Please send me an e-mail if you've got any questions or suggestions about this CppUnit-Tutorial.