...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Contexts are a facility provided by the Unit Test Framework in order to be able to trace the location of assertions better. To grasp the idea, consider the following example:
void test_operations(Processor& processor, int limit) { for (int i = 0; i < limit; ++i) { BOOST_TEST(processor.op1(i)); for (int j = 0; j < i; ++j) { BOOST_TEST(processor.op2(i, j)); } } }
In case of failure, in order to see in the logs at which point of the loops the failure occurred, we need some extra information in the assertion:
BOOST_TEST(processor.op1(i));
replaced by
BOOST_TEST_MESSAGE(processor.op1(i), "With parameter i = " << i);
We see in this trivial example that a context, which is the variable i
in this case, should be acknowledged
by the assertion BOOST_CHECK
in a particular way. In the approach above, this is done by adding a message
to the assertion itself.
What if the context is more complex than that? In case the complexity of the context increases, the fact that the assertion and the context is tightly coupled as in the approach above is difficult to maintain:
void test_operations(Processor& processor, int limit, int level) { for (int i = 0; i < limit; ++i) { BOOST_TEST_MESSAGE(processor.op1(i), "With optimization level " << level << ", With parameter i = " << i); for (int j = 0; j < i; ++j) { BOOST_TEST_MESSAGE(processor.op2(i, j), "With optimization level " << level << ", With parameter i = " << i << ", With parameter j = " << j); } } } BOOST_AUTO_TEST_CASE(test1) { Processor processor; for (int level = 0; level < 3; ++level) { processor.optimization_level(level); test_operations(processor, 2, level); } }
Note the length of the messages, the repetition, and the fact, that we
pass argument level
to
function test_operations
only for the sake of generating an error message in case of a failure.
Therefore, loose coupling between the context of an assertion and the assertion point is a property that is desirable.
Macro BOOST_TEST_INFO
can
be used to define an error message to be bound to the first following assertion.
If (and only if) the assertion fails, the bound message will be displayed
along:
Code |
---|
#define BOOST_TEST_MODULE example80 #include <boost/test/included/unit_test.hpp> void test() { BOOST_TEST(false); } BOOST_AUTO_TEST_CASE(test_case1) { BOOST_TEST_INFO("Alpha"); BOOST_TEST_INFO("Beta"); BOOST_TEST(true); BOOST_TEST_INFO("Gamma"); char a = 'a'; BOOST_TEST_INFO("Delt" << a); test(); } |
Output |
---|
> example Running 1 test case... test.cpp(14): error: in "test_case1": check false has failed Failure occurred in a following context: Gamma Delta *** 1 failures is detected in test module "example80" |
Observe the following things. The information composed inside BOOST_TEST_INFO
is bound only to the
first assertion following the declaration. Thus bound information is only
displayed if the assertion fails; otherwise the message is discarded. The
BOOST_TEST_INFO
declaration
does not have to immediately precede the assertion, it is allowed to intertwine
them with other instructions, they can even be declared in different scopes.
Therefore it is also possible to bind more than one information to a given
assertion.
With BOOST_TEST_INFO
, we
can improve our initial example as follows:
void test_operations(Processor& processor, int limit, int level) { for (int i = 0; i < limit; ++i) { BOOST_TEST_INFO("With optimization level " << level); BOOST_TEST_INFO("With parameter i = " << i); BOOST_TEST(processor.op1(i)); for (int j = 0; j < i; ++j) { BOOST_TEST_INFO("With optimization level " << level); BOOST_TEST_INFO("With parameter i = " << i); BOOST_TEST_INFO("With parameter j = " << j); BOOST_TEST(processor.op2(i, j)); } } } BOOST_AUTO_TEST_CASE(test1) { Processor processor; for (int level = 0; level < 3; ++level) { processor.optimization_level(level); test_operations(processor, 2, level); } }
Macro BOOST_TEST_CONTEXT
defines a diagnostic message and a scope. The message is bound to every
assertion in the scope, and is displayed along with every failed assertion.
Code |
---|
#define BOOST_TEST_MODULE example81 #include <boost/test/included/unit_test.hpp> void test() { BOOST_TEST(2 != 2); } BOOST_AUTO_TEST_CASE(test_case1) { BOOST_TEST_CONTEXT("Alpha") { BOOST_TEST(1 != 1); test(); BOOST_TEST_CONTEXT("Be" << "ta") BOOST_TEST(3 != 3); BOOST_TEST(4 == 4); } BOOST_TEST(5 != 5); } |
Output |
---|
> example Running 1 test case... test.cpp(20): error: in "test_case1": check 1 != 1 has failed [1 == 1] Failure occurred in a following context: Alpha test.cpp(14): error: in "test_case1": check 2 != 2 has failed [2 == 2] Failure occurred in a following context: Alpha test.cpp(24): error: in "test_case1": check 3 != 3 has failed [3 == 3] Failure occurred in a following context: Alpha Beta test.cpp(29): error: in "test_case1": check 5 != 5 has failed [5 == 5] *** 4 failures are detected in test module "example81" |
Observe the following things. After the BOOST_TEST_CONTEXT
macro we have a pair of braces: they define the scope in which the diagnostic
message is in effect. If there is no braces, the scope applies only to
the following statement. BOOST_TEST_CONTEXT
declarations can nest.
With BOOST_TEST_CONTEXT
,
we can further improve our initial example, by putting variable level
into a scope-level context and
not pass it as function parameter:
void test_operations(Processor& processor, int limit) { for (int i = 0; i < limit; ++i) { BOOST_TEST_INFO("With parameter i = " << i); BOOST_TEST(processor.op1(i)); for (int j = 0; j < i; ++j) { BOOST_TEST_INFO("With parameter i = " << i); BOOST_TEST_INFO("With parameter j = " << j); BOOST_TEST(processor.op2(i, j)); } } } BOOST_AUTO_TEST_CASE(test1) { Processor processor; for (int level = 0; level < 3; ++level) { BOOST_TEST_CONTEXT("With optimization level " << level) { processor.optimization_level(level); test_operations(processor, 2); } } }
If we observe that variable i
also applies in a certain scope, we can improve our example further still.
Code |
---|
#define BOOST_TEST_MODULE example82 #include <boost/test/included/unit_test.hpp> struct Processor { int level; void optimization_level(int l) { level = l; } bool op1(int) { return level < 2; } bool op2(int, int) { return level < 1; } }; void test_operations(Processor& processor, int limit) { for (int i = 0; i < limit; ++i) { BOOST_TEST_CONTEXT("With parameter i = " << i) { BOOST_TEST(processor.op1(i)); for (int j = 0; j < i; ++j) { BOOST_TEST_INFO("With parameter j = " << j); BOOST_TEST(processor.op2(i, j)); } } } } BOOST_AUTO_TEST_CASE(test1) { Processor processor; for (int level = 0; level < 3; ++level) { BOOST_TEST_CONTEXT("With optimization level " << level) { processor.optimization_level(level); test_operations(processor, 2); } } } |
Output |
---|
> example Running 1 test case... test.cpp(27): error: in "test1": check processor.op2(i, j) has failed Failure occurred in a following context: With optimization level 1 With parameter i = 1 With parameter j = 0 test.cpp(24): error: in "test1": check processor.op1(i) has failed Failure occurred in a following context: With optimization level 2 With parameter i = 0 test.cpp(24): error: in "test1": check processor.op1(i) has failed Failure occurred in a following context: With optimization level 2 With parameter i = 1 test.cpp(27): error: in "test1": check processor.op2(i, j) has failed Failure occurred in a following context: With optimization level 2 With parameter i = 1 With parameter j = 0 *** 4 failures are detected in the test module "example82" |