Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Testing with Fixtures

When we added our second test case in the previous tutorial, we ended up with some duplicated setup between the two test cases. This is a common occurrence in unit testing. We have some prerequisites for the system under test that must be prepared in order to exercise the system. In our case, it is a single output string stream that we use for capturing the output of our hello_world function.

Boost.Test provides a facility called a fixture for encapsulating repeated setup and tear down code between test cases. In our example, there isn't any explicit tear down code because the output string streams are created on the stack and destroyed when execution exits the test case. We have a little bit of repeated setup code that we can move to a fixture that is used by both our test cases. Let's put that repeated code in a fixture:

struct hello_world_fixture
{
    hello_world_fixture()
        : dest()
    { }
    ~hello_world_fixture()
    { }

    std::ostringstream dest;
};

BOOST_FIXTURE_TEST_CASE(hello_world_inserts_text, hello_world_fixture)
{
    hello_world(dest);

    BOOST_REQUIRE_EQUAL("Hello, world!\n", dest.str());
}

BOOST_FIXTURE_TEST_CASE(hello_world_stream_with_badbit_throws_runtime_error, hello_world_fixture)
{
    dest.clear(std::ios_base::badbit);

    BOOST_REQUIRE_THROW(hello_world(dest), std::runtime_error);
}

A fixture is simply a class or struct that is inherited by the test case. We connect the test case to the fixture by using BOOST_FIXTURE_TEST_CASE instead of BOOST_AUTO_TEST_CASE. The latter simply connects each test case to an empty fixture. After refactoring the test cases to use a fixture, we run the tests to make sure the tests still pass.

When using a fixture, the order of construction and destruction is as follows when a test case is executed:

  1. The fixture is constructed.
  2. The test case class is constructed.
  3. The test case test method is executed
  4. The test case class is destroyed.
  5. The fixture is destroyed.

This process happens for each test case that is executed, ensuring that each test case has a fresh fixture and remains independent of other test cases.

In most cases, the setup will be a little more involved than just creating an instance variable and there will be some work done in the constructor of the fixture. We could simply use the default constructor and destructor of our fixture in this case. We've written the constructor and destructor explicitly to emphasize that this is where setup and tear down occur.

Now that we've transformed the test cases to use a fixture, it seems we've simply traded one piece of repetition, the construction of the output string stream, for another: the repeated use of the fixture name and system under test in our test cases. We can use a custom test case macro to get rid of this duplication:

struct hello_world_fixture
{
    std::ostringstream dest;
};

#define HELLO_WORLD_TEST_CASE(name_) \
    BOOST_FIXTURE_TEST_CASE(hello_world_##name_, hello_world_fixture)

HELLO_WORLD_TEST_CASE(inserts_text)
{
    hello_world(dest);

    BOOST_REQUIRE_EQUAL("Hello, world!\n", dest.str());
}

HELLO_WORLD_TEST_CASE(stream_with_badbit_throws_runtime_error)
{
    dest.clear(std::ios_base::badbit);

    BOOST_REQUIRE_THROW(hello_world(dest), std::runtime_error);
}

Every test case has to have a unique name within the scope of its declaration, either the global namespace or a custom namespace, which is why we've been prefixing our test case names with the name of the system under test, hello_world. Once we accumulate a few tests, the chances for a clash between test names increases.

With our custom test case macro, we reduce the chance for a test case name clash by ensuring that every test case name is prefixed by the name of the system under test. We ensure that every test case for hello_world uses the same fixture and we gain some readability of the test cases by eliminating the distracting repetition. If we change the name of the fixture, then we need only edit the custom test case macro that supplies the fixture name.

In the next tutorial, we'll see an alternative way of eliminating this duplication of the prefixes of test case names.

Example Source Code

PrevUpHomeNext