About boost test

Boost provides a unit test framework which makes it possible to ensure that a class works well now, and for its all life, even if someone decides to change it later. This is a real save of time when the code is likely to be changed. Here is a little class we want to test: Parameter, at that moment, it stores an association of two string, a key and a value, and provides different ways to retrieve its data :

class                 Parameter
{
 private:
   std::string         mKey;
   std::string         mValue;
 public:

   Parameter(void);
   Parameter(const Parameter & aCopy);
   Parameter &         operator=(const Parameter & aCopy);
   ~Parameter();

   Parameter(const std::string & aKey);
   Parameter(const std::string & aKey, const std::string & aValue);
   bool                operator==(const Parameter & aValue) const;

   const std::string & getKey(void) const;
   const std::string & getValue(void) const;
   void                setKey(const std::string & aKey);
   void                setValue(const std::string & aValue);
};

Writing tests

First thing we have to do is creating a main that will run our tests:

#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>

This main will launch the test suites we write. Each test suite is defined by a list of tests applied to a class. Here, we have one class, so we have one test suite. Let's write a main frame where we will write Parameter's test suite:

#include <boost/test/unit_test.hpp>

#include "Parameter.hh"

BOOST_AUTO_TEST_SUITE(parameter_tests)

// Here we will write our tests

BOOST_AUTO_TEST_SUITE_END()

This declares a test suite called parameter_tests, we now have to write our tests in it, this is done with the macro BOOST_AUTO_TEST_CASE:

#include <boost/test/unit_test.hpp>

#include "Parameter.hh"

BOOST_AUTO_TEST_SUITE(parameter_tests)

BOOST_AUTO_TEST_CASE(constructors_test)
{
       // Here we write tests related to constructors
}

BOOST_AUTO_TEST_CASE(operators_test)
{
       // Here we write tests related to operators
}

BOOST_AUTO_TEST_SUITE_END()

Here we declare two tests, one for the construction of a Parameter, one for the operators, let's write their content, here again, boost provides some macros :

#include "Parameter.hh"

#include <boost/test/unit_test.hpp>
#include <string>

BOOST_AUTO_TEST_SUITE(ini_parameter_tests)

BOOST_AUTO_TEST_CASE(constructors_test)
{
  BOOST_MESSAGE("testing ini::parameter constructor/setters/getters");

  std::string           empty("");

  Parameter        a;
  BOOST_CHECK_EQUAL(a.getValue(), empty);
  BOOST_CHECK_EQUAL(a.getKey(), empty);

  // etc...
}

BOOST_AUTO_TEST_CASE(operators_test)
{
  BOOST_MESSAGE("testing ini::parameter operators");

  Parameter        a;
  Parameter        b;
  Parameter        c("key");

  BOOST_CHECK(a == b);
  BOOST_CHECK(!(a == c));
  BOOST_CHECK(c == c);
}

BOOST_AUTO_TEST_SUITE_END()

Running tests

That's it, let's compile it and link it with boost_test_exec_monitor:

$ make
c++ -I../ -I/usr/local/include/ -I../../ -c Runner.cpp
c++ -I../ -I/usr/local/include/ -I../../ -c ../Parameter.cpp
c++ -I../ -I/usr/local/include/ -I../../ -c ParameterTest.cpp
g++ -Wall *.o -o run_tests -L/usr/local/lib -lboost_test_exec_monitor
$
$ ./run_tests 
Running 2 test cases...
*** No errors detected

By default, the BOOST_MESSAGE macro doesn't print anything, this is because there are several levels of logging in boost test, it can be customized through the environment:

> export BOOST_TEST_LOG_LEVEL=message
> ./run_tests 
Running 2 test cases...
testing parameter constructor/setters/getters
testing parameter operators
*** No errors detected

Now let's see what happens when a test fails:

Running 2 test cases...
testing ini::parameter constructor/setters/getters
ParameterTest.cpp(43): error in "constructors_test": check a.getKey() == d.getKey() failed [ != key]
ParameterTest.cpp(44): error in "constructors_test": check a.getValue() == d.getValue() failed [ != value]
testing ini::parameter operators

*** 2 failures detected in test suite "Master Test Suite"

Fixtures

What if we have another class called X that contains several instances of Parameter? How to test it? We need to create an instance of X and feed it with Parameters each time we need to make a test, this is boring. Fortunately, fixtures are here to simplify this task: let's write a XFixture class, that contains a public instance of X called mX;

struct XFixture
{
      XFixture()
      {
              mX.addNewParam(new Parameter("a"));
              mX.addNewParam(new Parameter("b", "val1"));
              mX.addNewParam(new Parameter("c", "val2"));
      }

     ~XFixture()
     {
          ;
     }

     X      mX;
};

Now, each time we need to write a test that requires the initialized instance of X, we only have to declare the test with the help of the macro BOOST_FIXTURE_TEST_CASE:

BOOST_AUTO_TEST_SUITE(x_tests)

BOOST_FIXTURE_TEST_CASE(xTest, XFixture)
{
     // here mX is available directly (public instance from XFixture)
}

BOOST_AUTO_TEST_SUITE_END()

Fixtures are reinitialized for each test, so it's ok not to restore them to their initial state.

Conclusion

Boost test provides a way to quickly write test with a few macros, taking over repetitive tasks. Unfortunately, the documentation is quite hard to begin with, (this is why I've written this article). I haven't tested yet other unit testing frameworks (cpptest? cppunit? ...), and I hope I will have time to try them.