Index TableOfContents
About this document
This document describes in brief how to install the [http://sourceforge.net/projects/cppunit/ CppUnit] framework under Unix and how to write and run test programs with it. This should get you started with using CppUnit.
The document is based on the [http://cppunit.sourceforge.net/doc/lastest/cppunit_cookbook.html CppUnit CookBook]. The examples and many explanations are taken almost verbatim from there, slightly changed and improved for readability.
References
The project's homepage is http://sourceforge.net/projects/cppunit.
The latest Doxygen documentation of the project's class hierarchy is http://cppunit.sourceforge.net/doc/lastest/index.html. This may be useful while you read this document.
Installing
Download the latest version of CppUnit from http://sourceforge.net/projects/cppunit. At the time of this writing, it is http://downloads.sourceforge.net/cppunit/cppunit-1.12.1.tar.gz.
Let us install it under /var/tmp/cppunit/install:
$ cd /var/tmp/ $ mv ~/cppunit-1.12.1.tar.gz . $ tar xzf cppunit-1.12.1.tar.gz $ ln -s cppunit-1.12.1 cppunit $ cd cppunit $ ./configure --prefix=`pwd`/install $ make $ make check $ make install
Writing a simplistic test case
Suppose we are developing a class Complex for dealing with complex numbers. In the following code, the operator+ intentionally contains a bug:
// file Complex.h class Complex { friend bool operator==(const Complex& a, const Complex& b); friend Complex operator+(const Complex& a, const Complex& b); double real, imaginary; public: Complex( double r, double i = 0 ) : real(r), imaginary(i) {} };
// file Complex.cpp #include "Complex.h" bool operator==( const Complex &a, const Complex &b ) { return a.real == b.real && a.imaginary == b.imaginary; } Complex operator+( const Complex &a, const Complex &b ) { // BUG! imaginary part should be 'a.imaginary + b.imaginary' return Complex(a.real + b.real, a.imaginary + b.real); }
Now let us write a CppUnit test case for this class.
For this, we have to subclass the TestCase class and to override the method runTest(). To check a value, we call CPPUNIT_ASSERT(bool) and pass in an expression that is true if the test succeeds.
// file ComplexNumberTest.h #include <cppunit/TestCase.h> class ComplexNumberTest : public CppUnit::TestCase { public: ComplexNumberTest( std::string name ); void runTest(); };
// file ComplexNumberTest.cpp #include "Complex.h" #include "ComplexNumberTest.h" ComplexNumberTest::ComplexNumberTest( std::string name ) : CppUnit::TestCase( name ) {} void ComplexNumberTest::runTest() { CPPUNIT_ASSERT( Complex (10, 1) == Complex (10, 1) ); CPPUNIT_ASSERT( !(Complex (1, 1) == Complex (2, 2)) ); CPPUNIT_ASSERT( Complex (1, 2) + Complex(3, 4) == Complex (4, 6)); }
The main program creates a TestCase object and calls its runTest() method:
// file runComplexNumberTest.cpp #include "ComplexNumberTest.h" int main() { ComplexNumberTest myTest("My first CppUnit test"); myTest.runTest(); }
Compiling and running
The following makefile based on implicit rules will do the job for us:
INCLS = -I/var/tmp/cppunit/install/include/ LIBDIRS = -L/var/tmp/cppunit/install/lib LIBS = -lcppunit -ldl # note that CppUnit needs libld.so! # Implicit rule: How to make an .o-file from a .cpp-file %.o: %.cpp ${CXX} -c $< ${CXXFLAGS} ${INCLS} # Implicit rule: How to make an executable from .o-files %: %.o ${CXX} -o $@ $^ ${LIBDIRS} ${LIBS} runComplexNumberTest: runComplexNumberTest.o ComplexNumberTest.o Complex.o runtest: runComplexNumberTest export LD_LIBRARY_PATH=/var/tmp/cppunit/install/lib && ./runComplexNumberTest
Note that CppUnit needs the library ld.so in the linking step.
Now we can compile and run the program:
$ make runtest cc -c runComplexNumberTest.cpp -Wall -I/var/tmp/cppunit/install/include/ cc -c ComplexNumberTest.cpp -Wall -I/var/tmp/cppunit/install/include/ cc -c Complex.cpp -Wall -I/var/tmp/cppunit/install/include/ cc -o runComplexNumberTest runComplexNumberTest.o ComplexNumberTest.o Complex.o -L/var/tmp/cppunit/install/lib -lcppunit -ldl export LD_LIBRARY_PATH=/var/tmp/cppunit/install/lib && ./runComplexNumberTest terminate called after throwing an instance of 'CppUnit::Exception' what(): assertion failed - Expression: Complex (1, 2) + Complex(3, 4) == Complex (4, 6) /bin/sh: line 1: 10793 Aborted ./runComplexNumberTest make: *** [runtest] Error 134
As we can see, the third assertion failed, which detects the bug in our operator+.
Fixtures, Callers, Suites, and Runners
That was a very simple test. Ordinarily, we will have many little test cases that we will want to run on the same set of objects. To do this, we can use a "fixture".
Now you should maybe skim through the [http://cppunit.sourceforge.net/doc/lastest/cppunit_cookbook.html CppUnit CookBook]. You will learn about TestFixtures, TestCallers, TestSuites, and TestRunners.
In short:
A TestFixture is a set of objects that serves as a base for a set of test cases, together with methods that implement these test cases. A TestFixture is used to provide a common environment for a set of test cases. Each test runs in its own fixture so there can be no side effects among test runs.
A TestCaller runs a particular method (test case) of a fixture.
A TestSuite runs any number of callers together.
A TestRunner runs a suite and displays its results.
In a fixture, we can
- Add member variables for each part of the fixture
- Override setUp() to initialize the variables
- Override tearDown() to release any permanent resources we allocated in setUp()
Here is a test fixture for our Complex class. It allocates three Complex objects to be used in its test cases. It implements two test cases, testEquality() and testAddition(). It overrides the suite() method to create and return a test suite consisting of its two test cases.
setup() and tearDown() are called before and after every test case in the fixture, respectively.
// file ComplexNumberTestFixture.h // A test fixture for Complex, returning a test suite to be run by a runner #include <cppunit/TestFixture.h> #include <cppunit/TestSuite.h> #include <cppunit/TestCaller.h> #include "Complex.h" class ComplexNumberTestFixture : public CppUnit::TestFixture { private: Complex *a, *b, *s; public: void setUp(); void tearDown(); void testEquality(); void testAddition(); static CppUnit::Test* suite(); };
// file ComplexNumberTestFixture.cpp #include "ComplexNumberTestFixture.h" void ComplexNumberTestFixture::setUp() { a = new Complex( 1, 2 ); b = new Complex( 3, 4 ); s = new Complex( 4, 6 ); } void ComplexNumberTestFixture::tearDown() { delete a; delete b; delete s; } void ComplexNumberTestFixture::testEquality() { CPPUNIT_ASSERT( *a == *a ); CPPUNIT_ASSERT( !(*a == *b) ); } void ComplexNumberTestFixture::testAddition() { CPPUNIT_ASSERT( *a + *b == *s ); } CppUnit::Test* ComplexNumberTestFixture::suite() { CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "ComplexNumberTest" ); suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTestFixture>( "testEquality", &ComplexNumberTestFixture::testEquality ) ); suiteOfTests->addTest( new CppUnit::TestCaller<ComplexNumberTestFixture>( "testAddition", &ComplexNumberTestFixture::testAddition ) ); return suiteOfTests; }
To run test the suite returned by the fixture's suite() method, we pass it to a TestRunner object and call its run() methdod:
// file runTestSuite.cpp #include <cppunit/ui/text/TestRunner.h> #include "ComplexNumberTestFixture.h" int main() { CppUnit::TextUi::TestRunner runner; runner.addTest( ComplexNumberTestFixture::suite() ); //runner.addTest( AnotherTestFixture::suite() ); //runner.addTest( AnExampleTestCase::suite() ); // and maybe add more suites runner.run(); }
The TestRunner will run the tests. If all the tests pass, we shall get an informative message. If any fail, we shall get the following information:
- The name of the test case that failed
- The name of the source file that contains the test
- The line number where the failure occurred
All of the text inside the call to CPPUNIT_ASSERT() which detected the failure
The output of the above program is
..F !!!FAILURES!!! Test Results: Run: 2 Failures: 1 Errors: 0 1) test: testAddition (F) line: 27 ComplexNumberTestFixture.cpp assertion failed - Expression: *a + *b == *s
Helper macros
Implementing the static suite() method in a TestFixture is a repetitive and error prone task. A set of macros have been created to automatically implement this method.
The following code is a rewrite of ComplexNumberTestFixture using these macros:
// file ComplexNumberTestFixturewithMacro.h // A test fixture for Complex, returning a test suite to be run by a runner #include <cppunit/TestFixture.h> #include <cppunit/TestSuite.h> #include <cppunit/TestCaller.h> #include <cppunit/extensions/HelperMacros.h> #include "Complex.h" class ComplexNumberTestFixtureWithMacro : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE( ComplexNumberTestFixtureWithMacro ); CPPUNIT_TEST( testEquality ); CPPUNIT_TEST( testAddition ); CPPUNIT_TEST_SUITE_END(); // remainder as in the class above, but without suite() private: Complex *a, *b, *s; public: void setUp(); void tearDown(); void testEquality(); void testAddition(); }; // of course, the implementation of suite() is also missing in the .cpp file ;-)
There are other helper macros, for example, to check that a specific exception is thrown under specific circumstances. Check the documentation.
Test factory registry
It is easy to forget to add a fixture suite to the test runner since it is in another file.
Compilation bottleneck may be caused by the inclusion of all test case headers.
To solve these problems, CppUnit introduced TestFactoryRegistry objects. Such an object is a place where suites can be registered at initialization time.
For example, to register the ComplexNumberTestFixture suite in the registry, we would add 2 lines in the .cpp file:
// file ComplexNumberTestFixture.cpp #include <cppunit/extensions/HelperMacros.h> #include "ComplexNumberTestFixture.h" CPPUNIT_TEST_SUITE_REGISTRATION( ComplexNumberTestFixture ); // now as before...
Then, to run the tests using the text test runner, we do not need to include the fixture anymore:
// file runTestSuiteWithTestFactoryRegistry.cpp #include <cppunit/extensions/TestFactoryRegistry.h> #include <cppunit/ui/text/TestRunner.h> int main() { CppUnit::TextUi::TestRunner runner; CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry(); runner.addTest( registry.makeTest() ); runner.run(); }
That is, first we retrieve the instance of the TestFactoryRegistry. Then we obtain and add a new TestSuite created by the TestFactoryRegistry that contains all the test suites registered using CPPUNIT_TEST_SUITE_REGISTRATION().
You may wonder from where the runner now knows what tests to add: This is specified by simply linking the ComplexNumberTestFixture.o object file to the main object file runTestSuiteWithTestFactoryRegistry.o.
Automated post-build checks
How do we integrate unit testing to our build process? TestRunner::run() returns a boolean indicating wether the run was successful. We can just return this value from main():
#include <cppunit/extensions/TestFactoryRegistry.h> #include <cppunit/ui/text/TestRunner.h> int main() { CppUnit::TextUi::TestRunner runner; CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry(); runner.addTest( registry.makeTest() ); bool wasSuccessful = runner.run( "", false ); return wasSuccessful ? 0 : 1; }
Now, we can run our test program after each compilation of our tested application. This can be formulated by an additional command in the makefile. If there is an error (the exit status is nonzero), the make utility gives up on the current rule, and perhaps on all rules.