Getting started with CPPUnit

Unit testing is beneficial for every software project, even randomized algorithms like metaheuristics and genetic algorithms. I want to just stress one benefit, unit testing encourages developers to write code with minimal dependencies, just because otherwise tests are hard to write. The fact that slows development velocity in terms of features per time unit is the increase in interactions between various features. The n+1 feature added may interact with n already available features in many ways. These n+1 features have n2 possible interactions. When dependencies are limited, possible feature interactions are also limited and the project complexity stays under control.

Installing CPPunit

This step covers Ubuntu/debian way to install it. To install the development libraries, for a development machine, install libcppunit-dev.

sudo apt-get install libcppunit-dev

Creating CPPUnit tests

Let’s assume we want to develop an add(int, int) method to perform addition. We would start with a test fixture:

class AddTest : public CppUnit::TestFixture {
private:
        CPPUNIT_TEST_SUITE( AddTest );
        CPPUNIT_TEST( runAddTest );
        CPPUNIT_TEST_SUITE_END();

public:
        void setUp() {}

        void tearDown() {}

        void runAddTest() {
                CPPUNIT_ASSERT( 5 == add(2,3) );
                CPPUNIT_ASSERT( 20 == add(10,10) );
        }
};

CPPUNIT_TEST_SUITE_REGISTRATION( AddTest);

This class defines the test methods within a named test suite. Here the test method is runAddTest(). The name of the test suite must be the same as the class. The method runAddTest() makes some assertions about expected results of the add() function.

The last line is a macro to register the AddTest suite, in order to run it. Also, methods setUp() and tearDown() are used to prepare the environment before the test methods are invoked and clean up or release resources afterwords.

We are going to need two more files for the add() definition:
The add.h file:

#ifndef __ADD_H_INCLUDED__
#define ifndef __ADD_H_INCLUDED__

int add(int a, int b);

#endif

The add.cpp file.

int add(int a, int b)
{
	return 0;
}

Running tests

In order to run the tests, the complete source files presented below are used. The first step is to verify that our add method does not work.

$ make tester
./test

 === Running Tests
AddTest::runAddTest : assertion

 === Test Results
test.cpp:26:Assertion
Test name: AddTest::runAddTest
assertion failed
- Expression: 5 == add(2,3)

Failures !!!
Run: 1   Failure total: 1   Failures: 1   Errors: 0
make: *** [tester] Error 1

It says that add(2,3) method should return 5. Let’s satisfy it, modifying file add.cpp:

int add(int a, int b)
{
	return 5;
}
$ make tester
g++   -lcppunit  test.cpp add.cpp add.h   -o test
./test

 === Running Tests
AddTest::runAddTest : assertion

 === Test Results
test.cpp:27:Assertion
Test name: AddTest::runAddTest
assertion failed
- Expression: 20 == add(10,10)

Failures !!!
Run: 1   Failure total: 1   Failures: 1   Errors: 0
make: *** [tester] Error 1

Now it failed later, so let’s really do it.

int add(int a, int b)
{
	return a+b;
}

Now the result is:

$ make tester
g++   -lcppunit  test.cpp add.cpp add.h   -o test
./test

 === Running Tests
AddTest::runAddTest : OK

 === Test Results
OK (1)

Now the test is satisfied, time to celebrate!

Closing

Adding tests to code is not hard, we do test our code either way. Testing using an automated framework ensures that things don’t break during development, document the code and increase developer confidence for the code. Automated testing are a key element to prevent developer velocity to decrease over time, as a consequence of the increased project complexity.

An interesting feature not covered here is the XML format output, which is compatible with continuous integration tools – see Jenkins or Hudson. Continuous integration tools are able to checkout the source code from of the source control system (i.e. subversion, mercurial, git), build the project and run tests against it. The XML format that CPPunit produces is directly usable by those tools.

Complete source files

The main.cpp file, which is the main program – not very useful, but needed for completeness of the scenario.

#include <stdio.h>

int add(int, int);

int main()
{
	int a, b;
	a = 10;
	b = 5;
	printf("%d + %d = %d\n", a,b, add(a,b) );

}

The Makefile to build the project:

DEPS	:= add.cpp add.h
TARGET	:= main

${TARGET}: main.cpp ${DEPS}

.PHONY:	tester

test:	LDFLAGS += -lcppunit
test:	test.cpp ${DEPS}

tester: test
	./test

clean:
	$(RM)  main test

The complete test code, file test.cpp, is:

#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestRunner.h>
#include <cppunit/TestResultCollector.h>

#include "add.h"

class AddTest : public CppUnit::TestFixture {
private:
        CPPUNIT_TEST_SUITE( AddTest);
        CPPUNIT_TEST( runAddTest );
        CPPUNIT_TEST_SUITE_END();

public:
        void setUp() {}

        void tearDown() {}

        void runAddTest() {
                CPPUNIT_ASSERT( 5 == add(2,3) );
                CPPUNIT_ASSERT( 20 == add(10,10) );
        }

};

CPPUNIT_TEST_SUITE_REGISTRATION( AddTest);

// Don't need to touch main, it's ok
int main(int argc, char** argv)
{
        // Create the event manager and test controller
        CppUnit::TestResult controller;
        // Add a listener that colllects test result
        CppUnit::TestResultCollector result;
        controller.addListener( &result );
        // Add a listener that print dots as test run.
        CppUnit::BriefTestProgressListener progress;
        controller.addListener( &progress );

        CppUnit::TextUi::TestRunner runner;
        CppUnit::TestFactoryRegistry &registry = CppUnit::TestFactoryRegistry::getRegistry();
        runner.addTest( registry.makeTest() );
        std::cout << std::endl << " === Running Tests " << std::endl;
        runner.run(controller);
        // Print test in a compiler compatible format.
        std::cout << std::endl << " === Test Results" << std::endl;
        CppUnit::CompilerOutputter outputter( &result, CppUnit::stdCOut() );
        //CppUnit::TextOutputter outputter( &result, CppUnit::stdCOut() );
        outputter.write();

        return result.wasSuccessful() ? 0 : 1;
}
Advertisements

Posted on March 15, 2012, in Programming and tagged , , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: