Home 

Unit testing HOWTO

This section of the developer handbook should give some examples on how to organize test cases and how to setup a test environment.

My examples will all be based on the code of the KMyMoney engine found in the subdirectory kmymoney2/kmymoney2/mymoney and it's subdirectory storage. A single executable exists that contains all the test code for the engine. It's called autotest and resides in the mymoney subdirectory.

Integration of CPPUNIT into the KMyMoney project

The information included in the following is based on version 1.8.0 of CPPUNIT. The KMyMoney build system has been enhanced to check for it's presence. Certain definitions setup by automake/configure allow to compile the project without unit testing support.

Caution

This is not the recommended way for developers!

If code within test environments is specific to the presence of CPPUNIT it can be included in the following #ifdef primitive:


#ifdef HAVE_LIBCPPUNIT
// specific code that should only be compiled,
// if CPPUNIT >= 1.8.0 is present
#endif


For an example see the Unit Test Container Source File Example.

The same applies for directives that are used in Makefile.am files. The primitive to be used there is as follows:


if CPPUNIT

# include automake-directives here, that should be evaluated
# only, when CPPUNIT is present

else

# include automake directives here, that should be evaluated
# only, when CPPUNIT is not present.

endif


For an example see kmymoney2/mymoney/Makefile.am.

Naming conventions

The test containers are also classes. Throughout CPPUNIT, the test containers are referred to as test fixtures. In the following, I use both terms. For a given class MyMoneyAbc, which resides in the files mymoneyabc.h and mymoneyabc.cpp, the test container is named MyMoneyAbcTest and resides in the files mymoneyabctest.h and mymoneyabctest.cpp in the same directory. The test container must be derived publicaly from CppUnit::TestFixture. Each testcase is given a descriptive name (e.g. EmptyConstructor) and I found it useful to prefix this name with the literal 'test' resulting into something like testEmptyConstructor.

Necessary include files

In order to use the functionality provided by CPPUNIT, one has to include some information provided with CPPUNIT in the test environment. This is done with the following include primitive as one of the first things in the header file of the test case container (e.g. mymoneyabctest.h):


#include <cppunit/extensions/HelperMacros.h>

Accessing private members

For the verification process it is sometimes necessary to look at some internal states of the object under test. Usually, all this information is declared private in the class and only accessible through setter and getter methods. Cases exist, where these methods are not implemented on purpose and thus accessing the information from the test container is not possible.

Various mechanism have been developed all with pros and cons. Throughout the test containers I wrote, I used the method of redefining the specifier private through public but only for the time when reading the header file of the object under test. This can easily be done by the C++ preprocessor. The following example shows how to do this:


#define private public
#include "mymoneyabc.h"
#undef private


The same applies to protected members. Just add a line containing #define protected public before including the class definition and a line containing #undef protected right after the inclusion line.

Standard methods for each testcase

Three methods must exist for each test fixture. These are a default constructor, setUp and tearDown. I think, it is not necessary to explain the default constructor here. SetUp and tearDown have a special function within the test cases. setUp() will be called before the execution of any test case in the test fixture. tearDown() will be called after the execution of the test case, no matter if the test case passes or fails. Thus setUp() is used to perform initialization necessary for each test case in the fixture and tearDown() is used to clean things up. setUp() and tearDown() should be written in such a way, that all objects created through a test case should be removed by tearDown(), i.e. the environment is restored exactly to the state it was before the call to setUp().

Note

This is not always the case within the testcase for KMyMoney. Espacially when using a database as the permanent storage things have to be overhauled for e.g. MyMoneyFileTest.

CPPUNIT comes with a set of macros that help writing testcases. I cover them here briefly. If you wish a more detailed description, please visit the CPPUNIT project homepage.

CPPUNIT_ASSERT

This is the macro used at most throughout the test cases. It checks, that a given assumption is true. If it is not, the test case fails and a respective message will be printed at the end of the testrun.

CPPUNIT_ASSERT has a single argument which is a boolean expression. The expression must be true in order to pass the test. If it is false, the test case fails and no more code of the test case is executed. The following example shows how the macro is used:


  int a, b;
  a = 0, b = 1;
  CPPUNIT_ASSERT(a != b);
  a = 1;
  CPPUNIT_ASSERT(a == b);


The example shows, how two test steps are combined. One checks the inequality of two integers, one the equality of them. If either one does not work, the test case fails.

See the Unit Test Source File Example for a demonstration of it's use.

CPPUNIT_FAIL

This is the macro used when the execution of a test case reaches a point it should not. This usually happens, if exceptions are thrown or not thrown.

CPPUNIT_FAIL has a single argument which is the error message to be displayed. The following example shows how the macro is used:


  int a = 1, b = 0;
  try {
    a = a / b;
    CPPUNIT_FAIL("Expected exception missing!");
  } catch (exception *e) {
    delete e;
  }

  try {
    a = a / a;
  } catch (exception *e) {
    delete e;
    CPPUNIT_FAIL("Unexpected exception!");
  }
  

The example shows, how two test steps are combined. One checks the occurance of an exception, the other one that no exception is thrown. If either one does not work, the test case fails.

CPPUNIT_TEST_SUITE

This macro is used as the first thing in the declaration of the test fixture. A single argument is the name of the class for the test fixture. It starts the list of test cases in this fixture defined by the CPPUNIT_TEST macro. The list must be terminated using the CPPUNIT_TEST_SUITE_END macro.

See the Unit Test Header File Example for a demonstration of it's use.

CPPUNIT_TEST_SUITE_END

This macro terminates the list of test cases in a test fixture. It has no arguments.

See the Unit Test Header File Example for a demonstration of it's use.

CPPUNIT_TEST

This macro defines a new test case within a test fixture. As argument it takes the name of the test case.

See the Unit Test Header File Example for a demonstration of it's use.

CPPUNIT_TEST_SUITE_REGISTRATION

This macro registers a test fixture within a test suite. It takes the name of the test fixture as argument.

See the Unit Test Container Source File Example for a demonstration of it's use.