C++ Unit Tests With Boost.Test, Part 3

In Part 2 of this series, I discussed the assertion facilities of Boost.Test and how to get the first passing test on Bob Martin’s Prime Factors Kata. In this part, I will continue with test-driven development to complete the functionality of the PrimeFactors::Generate method.

Failing Test: TwoHasOneFactor

Let’s continue with the programming kata by adding our second failing test. Add the following code to TestPrimeFactors.cpp:

BOOST_AUTO_TEST_CASE(TwoHasOneFactor)
{
    std::list<int> result = PrimeFactors::Generate(2);
    int expected[] = { 2 };
    BOOST_REQUIRE_EQUAL_COLLECTIONS(&expected[0], &expected[1],
        result.begin(), result.end());
}

In this test case, we’re leveraging the ability of Boost.Test to compare the contents of collections. Remember that pointers are implementations of the iterator concept, so we can use a pointer to the first element and a pointer just past the last element of an array as iterators for the begin and end of the array.

Build the solution and you should get a failing build with output similar to the following:

1>------ Build started: Project: Test, Configuration: Debug Win32 ------
1>Compiling...
1>TestPrimeFactors.cpp
1>Linking...
1>Embedding manifest...
1>Running unit tests...
1>Running 2 test cases...
1>c:/tmp/code/boost.test/test/testprimefactors.cpp(15): fatal error in "TwoHasOneFactor":
    critical check { &expected[0], &expected[1] } == { result.begin(), result.end() } failed. 
1>Collections size mismatch: 1 != 0
1>*** 1 failure detected in test suite "Master Test Suite"
1>Project : error PRJ0019: A tool returned an error code from "Running unit tests..."
1>Build log was saved at "file://c:\tmp\Code\Boost.Test\Test\Debug\BuildLog.htm"
1>Test - 2 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 1 up-to-date, 0 skipped ==========

Passing Test: TwoHasOneFactor

Change the implementation of Generate to the following code to pass the test:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        primes.push_back(2);
    }
    return primes;
}

Build the solution to obtain a passing test and output similar to the following in the Output window:

1>------ Build started: Project: PrimeFactors, Configuration: Debug Win32 ------
1>Compiling...
1>PrimeFactors.cpp
1>Creating library...
1>Build log was saved at "file://c:\tmp\Code\Boost.Test\PrimeFactors\Debug\BuildLog.htm"
1>PrimeFactors - 0 error(s), 0 warning(s)
2>------ Build started: Project: Test, Configuration: Debug Win32 ------
2>Linking...
2>Embedding manifest...
2>Running unit tests...
2>Running 2 test cases...
2>*** No errors detected
2>Build log was saved at "file://c:\tmp\Code\Boost.Test\Test\Debug\BuildLog.htm"
2>Test - 0 error(s), 0 warning(s)
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

From now on, I will omit the build output as it will always be similar to what I’ve shown above.

Failing Test: ThreeHasOneFactor

Add the following test case to TestPrimeFactors.cpp:

BOOST_AUTO_TEST_CASE(ThreeHasOneFactor)
{
    std::list<int> result = PrimeFactors::Generate(3);
    int expected[] = { 3 };
    BOOST_REQUIRE_EQUAL_COLLECTIONS(&expected[0], &expected[1],
        result.begin(), result.end());
}

Passing Test: ThreeHasOneFactor

Change the implementation of Generate to pass the test. Replace

primes.push_back(2);

with

primes.push_back(n);

Now we have a passing test.

Red, Green, Refactor!

Whenever your tests are green, you have an opportunity to refactor with a safety net. Refactoring is changing the design of existing code without changing its functionality. So how do we know the changes we’re making don’t affect the functionality? We know this because the tests still pass. In test-driven development, we get into a rhythm of red, green, refactor to improve our code incrementally. First we go red to specify some new functionality that we want. Next we go green as quickly as possible (even if the code isn’t pretty) so that we can lock in the functionality. Once we’re green, we have a safety net of tests to let us refactor the design of the code to improve it.

Its just as important to refactor your test code as it is to refactor your production code. The tests are part of the documentation of your production code: they document the expected behavior for various conditions. They are better than a requirements document because they are executable and immediately give us a green or red indicator on the functionality of the code.

In our test code, we’ve accumulated a little duplication with this last test case. We have duplicated code that invokes the production code and compares the results to an array of integers. We can extract this duplicated code into a custom assertion with an intention revealing name:

static void AssertValueHasPrimeFactors(int value,
    int const *expected, int numExpected)
{
    std::list<int> result = PrimeFactors::Generate(value);
    BOOST_REQUIRE_EQUAL_COLLECTIONS(&expected[0], &expected[numExpected],
        result.begin(), result.end());
}

Now we can eliminate the duplication in our test cases:

BOOST_AUTO_TEST_CASE(TwoHasOneFactor)
{
    int const expected[] = { 2 };
    AssertValueHasPrimeFactors(2, expected, 1);
}

BOOST_AUTO_TEST_CASE(ThreeHasOneFactor)
{
    int const expected[] = { 3 };
    AssertValueHasPrimeFactors(3, expected, 1);
}

After making this change, we run our test again to make sure the functionality is preserved.

Failing Test: FourHasTwoFactors

Add the following code to TestPrimeFactors.cpp as our next test case:

BOOST_AUTO_TEST_CASE(FourHasTwoFactors)
{
    int const expected[] = { 2, 2 };
    AssertValueHasPrimeFactors(4, expected, 2);
}

Passing Test: FourHasTwoFactors

Change the implementation of Generate to the following code:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        if (n % 2 == 0)
        {
            primes.push_back(2);
            n /= 2;
        }
        if (n > 1)
        {
            primes.push_back(n);
        }
    }
    return primes;
}

Our new test case is now passing. There isn’t any duplication we can eliminate by refactoring, so we can move on to the next test case.

Test: SixHasTwoFactors

Add the following test case to TestPrimeFactors.cpp:

BOOST_AUTO_TEST_CASE(SixHasTwoFactors)
{
    int const expected[] = { 2, 3 };
    AssertValueHasPrimeFactors(6, expected, 2);
}

Adding this test case does not cause a test failure. That is because our existing implementation is already smart enough to cover this case in addition to all our previous cases. Should we keep this test case? Sure, why not. It covers functionality that we want. We skipped the test case for the input 5 because we didn’t think it was going to tell us anything different than the test case for 3. We thought there might be something new in the test case for 6, but it turns out our algorithm is already smart enough to handle it. Let’s proceed to the next test case.

Failing Test: EightHasThreeFactors

Add the following test case to TestPrimeFactors.cpp:

BOOST_AUTO_TEST_CASE(EightHasThreeFactors)
{
    int const expected[] = { 2, 2, 2 };
    AssertValueHasPrimeFactors(8, expected, 3);
}

Passing Test: EightHasThreeFactors

Change the implementation of Generate by replacing:

if (n % 2 == 0)

with:

while (n % 2 == 0)

The test should now pass.

Failing Test: NineHasTwoFactors

Add the following test case to TestPrimeFactors.cpp:

BOOST_AUTO_TEST_CASE(NineHasTwoFactors)
{
    int const expected[] = { 3, 3 };
    AssertValueHasPrimeFactors(9, expected, 2);
}

Passing Test: NineHasTwoFactors

Change the implementation of Generate to the following code to get a passing test:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        int candidate = 2;
        while (n > 1)
        {
            while (n % candidate == 0)
            {
                primes.push_back(candidate);
                n /= candidate;
            }
            candidate++;
        }
        if (n > 1)
        {
            primes.push_back(n);
        }
    }
    return primes;
}

Now that we have a passing test, we can look for opportunities to refactor. The final if clause is redundant since the while loop guarantees that n is not greater than one after the while loop executes. We can remove the if clause and rerun the tests to make sure we didn’t break anything. Generate now looks like this:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        int candidate = 2;
        while (n > 1)
        {
            while (n % candidate == 0)
            {
                primes.push_back(candidate);
                n /= candidate;
            }
            candidate++;
        }
    }
    return primes;
}

Similarly, we can condense the inner while loop into a for loop:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        int candidate = 2;
        while (n > 1)
        {
            for (; n % candidate == 0; n /= candidate)
            {
                primes.push_back(candidate);
            }
            candidate++;
        }
    }
    return primes;
}

After making the change, we build and run the tests again to confirm that we haven’t broken any behavior. We can apply the same change to condense the outer while loop into a for loop:

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    if (n > 1)
    {
        for (int candidate = 2; n > 1; candidate++)
        {
            for (; n % candidate == 0; n /= candidate)
            {
                primes.push_back(candidate);
            }
        }
    }
    return primes;
}

Finally, we can now see that the outer if test is redundant with the test in the outer for, yielding this version of Generate. Again, we run the tests after each change to make sure we haven’t broken any functionality. Since the tests are run on every build, we know that we haven’t broken the functionality covered by the tests by simply having a successful build.

std::list<int> PrimeFactors::Generate(int n)
{
    std::list<int> primes;
    for (int candidate = 2; n > 1; candidate++)
    {
        for (; n % candidate == 0; n /= candidate)
        {
            primes.push_back(candidate);
        }
    }
    return primes;
}

This completes the programming Kata. If you are not convinced that we have the whole algorithm, you can add more test cases. I encourage you to not just read this blog post, but practice the sequence of tests in this post.

In Part 4 of this series, I will cover test-driven development for UI behavior.

[Updated to favor BOOST_REQUIRE over BOOST_CHECK.]

3 Responses to “C++ Unit Tests With Boost.Test, Part 3”

  1. C++ Unit Tests With Boost.Test, Part 2 « Legalize Adulthood! Says:

    […] me of follow-up comments via email. « C++ Unit Tests With Boost.Test, Part 1 C++ Unit Tests With Boost.Test, Part 3 » Blog at WordPress.com. • Theme: Garland by Steven Wittens and Stefan […]

    Like

  2. C++ Unit Tests With Boost.Test, Part 4 « Legalize Adulthood! Says:

    […] Unit Tests With Boost.Test, Part 4 5-July-2009 — legalize In Part 3 of this series, we completed Bob Martin’s Prime Factors Kata using test-driven development […]

    Like

  3. Two Sessions Accepted for C++ Now! 2014 (May 12-17, 2014) | Legalize Adulthood! Says:

    […] tutorials I wrote in 2009 on practicing test-driven development using Boost.Test: Part 1, Part 2, Part 3, Part 4 and Part 5. You can also work through the Tutorials in my Boost.Test documentation […]

    Like


Leave a comment