Oct 13

Writing a custom check macro for the BOOST test library

Category: C++, Programming

In this article I demonstrate how to write your own check macros for the boost test library (not guaranteed to work in the future)

Full source code for this example can be downloaded here.

The problem

For the purposes of this article I would like to verify that a collection is monotonically increasing. There are a couple ways I can do this. The first is to just do it inline in every test:

...
if(begin != end){
    IteratorType previous = begin;
    IteratorType current = begin;
    current++;
    for(;current != end; current++){
        BOOST_CHECK_LT(*previous, *current);
    }
}
...

This approach is tedious and does not generate good error messages. Each failure generates an independent error message that doesn’t give any context such as the collection being checked or the index at which the error was found.

The second approach is to make a function:

template
bool check_monotonically_increasing(Iterator begin, Iterator end){
    ...
}
 
...
 
ITOS_CHECK(check_monotonically_increasing(begin, end));

This approach is less tedious, but now the error message only tells you the check failed without any context as to where and why. So clearly what we really want here is something like BOOST_CHECK_EQUAL_COLLECTIONS.

FTM_*_COLLECTION_MONO_INCREASE

The solution is to define a set of macros called FTM_*_COLLECTION_MONO_INCREASE that can be used as follows:

...
FTM_CHECK_COLLECTION_MONO_INCREASE(begin, end);
...

Where are the BOOST_* macros?

All of the code I looked at to create my own MACRO was located in boost/test_tools.hpp . This is the best place to start looking when you want to write your own.

Defining Your Test as a Functor

The first step to defining a custom test macro is to implement it as a functor. If you do now know what a functor is please refer to this article.

In this case, the functor must return a value of the type boost::test_tools::predicate_result. predicate_result combines a Boolean status with, when appropriate, an error message that will be displayed on failure.

namespace fm{namespace test{namespace detail{
 
struct check_mono_increase_func_frwd{
    template<typename ForwardIteratorA, typename ForwardIteratorB>
    boost::test_tools::predicate_result operator()(ForwardIteratorA begin,
        ForwardIteratorB end) const{
        boost::test_tools::predicate_result result = true;
        std::size_t pos = 0;
 
        if(begin == end)
            return result;
 
        ForwardIteratorA previous = begin;
        ForwardIteratorA current = begin;
        current++;
        while(current != end){
            if(*previous > *current){
                result = false;
                result.message() << "\n(position: " << pos << "): ";
                result.message() << *previous << " > " << *current;
            }
            previous = current;
            current++;
            pos++;
        }
 
        return result;
    }
};
 
}}}

Creating Your Macros

If you look in test_tools.hpp there are a number of different macros used to implement the various check macros. The key to this step is finding an example that is close to what you want and modifying it. BOOST_* macros are the most straight forward and BOOST_*_EXCEPTION and BOOST_*_EQUAL_COLLECTIONS provide examples of performing more complicated tasks.

In this case I’m going to essentially copy BOOST_*_EQUALS as follows:

#define FTM_WARN_COLLECTION_MONO_INCREASE(Begin, End) \
    BOOST_CHECK_WITH_ARGS_IMPL(::ftm::test::detail::check_mono_increase_func_frwd(), \
        "FTM_WARN_COLLECTION_MONO_INCREASE", \
        WARN, CHECK_PRED_WITH_ARGS, (Begin)(End));
 
#define FTM_CHECK_COLLECTION_MONO_INCREASE(Begin, End) \
    BOOST_CHECK_WITH_ARGS_IMPL(::ftm::test::detail::check_mono_increase_func_frwd(),
        "FTM_CHECK_COLLECTION_MONO_INCREASE", \
        CHECK, CHECK_PRED_WITH_ARGS, (Begin)(End));
 
#define FTM_REQUIRE_COLLECTION_MONO_INCREASE(Begin, End) \
    BOOST_CHECK_WITH_ARGS_IMPL(::ftm::test::detail::check_mono_increase_func_frwd(),
        "FTM_REQUIRE_COLLECTION_MONO_INCREASE", \
        REQUIRE, CHECK_PRED_WITH_ARGS, (Begin)(End));

Using the Macro

Now we can simply use the FTM macros like we’d use any of the BOOST test macros

BOOST_AUTO_TEST_CASE(test_increase){
    int test_array[] = {1,2,3,67,32,17,44,12};
 
    FTM_CHECK_COLLECTION_MONO_INCREASE(test_array, test_array + 8);
}

Final Thoughts

It’s worth noting that although adding your own boost test library macro can make unit tests a lot easier to write for your development team, the API doesn’t give me a lot of confidence that it was intended to be extended in this manner. I’m not sure what guarantee there is, if any, that what works in this release will work in the next.

The Author

Michael Smit is a software engineer in Seattle, Washington who works for amazon

Comments are off for this post

Comments are closed.