...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Caution | |
---|---|
the functionalities presented on this page have been superseded by the Data-driven test case facility. |
Some tests are required to be repeated for a series of different input parameters. One way to achieve this is manually register a test case for each parameter as in the previous examples. You can also invoke a test function with all parameters manually from within your test case, like this:
void single_test( int i ) { BOOST_CHECK( /* test assertion */ ); } void combined_test() { int params[] = { 1, 2, 3, 4, 5 }; std::for_each( params, params+5, &single_test ); }
The Unit Test Framework presents a better solution
for this problem: the unary function based test case, also referred as
parametrized test case. The unary test function can
be a free function, unary functor (for example created with boost::bind
) or unary method of a class with
bound test class instance). The test function is converted into test case
using the macro BOOST_PARAM_TEST_CASE
.
The macro expects a collection of parameters (passed as two input iterators)
and an unary test function:
BOOST_PARAM_TEST_CASE(test_function, params_begin, params_end);
BOOST_PARAM_TEST_CASE
creates
an instance of the test case generator. When passed to the method test_suite::add
,
the generator produces a separate sub test case for each parameter in the
parameters collection and registers it immediately in a test suite. Each
test case is based on a test function with the parameter bound by value,
even if the test function expects a parameter by reference. The fact that
parameter value is stored along with bound test function releases you from
necessity to manage parameters lifetime. For example, they can be defined
in the test module initialization function scope.
All sub test case names are deduced from the macro argument test_function
. If you prefer to assign
different names, you have to use the underlying make_test_case
interface
instead. Both test cases creation and registration are performed in the
test module initialization function.
The parametrized test case facility is preferable to the approach in the example above, since execution of each sub test case is guarded and counted independently. It produces a better test log/results report (in example above in case of failure you can't say which parameter is at fault) and allows you to test against all parameters even if one of them causes termination a particular sub test case.
In comparison with a manual test case registration for each parameter approach the parametrized test case facility is more concise and easily extensible.
In following simple example the same test, implemented in free_test_function
, is performed for
5 different parameters. The parameters are defined in the test module initialization
function scope. The master test suite contains 5 independent test cases.
Code |
---|
#include <boost/test/included/unit_test.hpp> #include <boost/test/parameterized_test.hpp> using namespace boost::unit_test; void free_test_function( int i ) { BOOST_TEST( i < 4 /* test assertion */ ); } test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) { int params[] = { 1, 2, 3, 4, 5 }; framework::master_test_suite(). add( BOOST_PARAM_TEST_CASE( &free_test_function, params, params+5 ) ); return 0; } |
Output |
---|
> example Running 5 test cases... test.cpp(15): error: in "free_test_function": check i < 4 has failed [4 >= 4] test.cpp(15): error: in "free_test_function": check i < 4 has failed [5 >= 4] *** 2 failures are detected in the test module "Master Test Suite" |
Next example is similar, but instead of a free function it uses a method of a class. Even though parameters are passed into test method by reference you can still define them in the test module initialization function scope. This example employs the alternative test module initialization function specification.
Code |
---|
#define BOOST_TEST_ALTERNATIVE_INIT_API #include <boost/test/included/unit_test.hpp> #include <boost/test/tools/floating_point_comparison.hpp> #include <boost/test/parameterized_test.hpp> #include <boost/bind/bind.hpp> using namespace boost::unit_test; namespace tt=boost::test_tools; class test_class { public: void test_method( double d ) { BOOST_TEST( d * 100 == (double)(int)(d*100), tt::tolerance(0.0001) ); } } tester; bool init_unit_test() { using boost::bind; using boost::placeholders::_1; double params[] = { 1., 1.1, 1.01, 1.001, 1.0001 }; boost::function<void (double)> test_method = bind( &test_class::test_method, &tester, _1); framework::master_test_suite(). add( BOOST_PARAM_TEST_CASE( test_method, params, params+5 ) ); return true; } |
Output |
---|
> example Running 5 test cases... test.cpp(23): error: in "test_method": check d * 100 == (double)(int)(d*100) has failed [1.0001 * 100 != 100]. Relative difference exceeds tolerance [0.001 > 0.0001] test.cpp(23): error: in "test_method": check d * 100 == (double)(int)(d*100) has failed [1.0001 * 100 != 100]. Relative difference exceeds tolerance [0.0001 > 0.0001] *** 2 failures are detected in the test module "Master Test Suite" |