...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Instead of comparing a single value against another, there is often a need for comparing collections of values. A collection and indirectly the values it contains may be considered in several ways:
N
values are stored in a container. Containers in this case are used
for storing several values, and iterating over the containers yields
sequences that can be compared element-wise.
The iteration should be in an order that is a priori
known [10], for being able to compare the sequences. The values in
the collection are independent each other, and subsets can be compared
as well.
N
representing a point in a N
dimensional space, compared to another point with the relation "<
": the comparison is application
specific and a possible comparison would be the lexicographical ordering
[11].
The following observations can be done:
BOOST_TEST
provides specific tools
for comparing collections:
More details about the concept of collection in the Unit Test Framework is given here.
The default comparison dispatches to the existing overloaded operator.
Given two containers c_a
and c_b
,
BOOST_TEST(c_a op c_b)
is equivalent, in terms of test success, to
auto result = c_a op c_b; BOOST_TEST(result);
In the example below, operator==
is not defined for std::vector
of different types, and the program would fail to compile if the corresponding
lines were uncommented (std::vector
uses lexicographical comparison by default).
Code |
---|
#define BOOST_TEST_MODULE boost_test_sequence #include <boost/test/included/unit_test.hpp> #include <vector> BOOST_AUTO_TEST_CASE( test_collections_vectors ) { std::vector<int> a{1,2,3}, c{1,5,3,4}; std::vector<long> b{1,5,3}; // the following does not compile //BOOST_TEST(a == b); //BOOST_TEST(a <= b); // stl defaults to lexicographical comparison BOOST_TEST(a < c); BOOST_TEST(a >= c); BOOST_TEST(a != c); } |
Output |
---|
> ./boost_test_container_default --log_level=all Running 1 test case... Entering test module "boost_test_sequence" test.cpp(13): Entering test case "test_collections_vectors" test.cpp(23): info: check a < c has passed test.cpp(24): error: in "test_collections_vectors": check a >= c has failed test.cpp(25): info: check a != c has passed test.cpp(13): Leaving test case "test_collections_vectors"; testing time: 208us Leaving test module "boost_test_sequence"; testing time: 286us *** 1 failure is detected in the test module "boost_test_container_default" |
By specifying the manipulator boost::test_tools::per_element
,
the comparison of the elements of the containers are performed element-wise,
in the order given by the forward iterators of the containers. This is
a comparison on the sequences of elements generated
by the containers, for which the Unit Test Framework
provides advanced diagnostic.
In more details, let c_a = (a_1,... a_n)
and c_b
= (b_1,... b_n)
be two sequences of same length, but not necessarily of same type. Those
sequences correspond to the content of the respective containers, in the
order given by their iterator. Let op
be one of the binary comparison
operators.
BOOST_TEST(c_a op c_b, boost::test_tools::per_element() );
is equivalent to
if(c_a.size() == c_b.size())
{
for(int i=0; i < c_a.size(); i++)
{
BOOST_TEST_CONTEXT
("index" << i);
BOOST_TEST(a_i op b_i);
}
}
else
{
BOOST_TEST(c_a.size() == c_b.size());
}
Warning | |
---|---|
this is fundamentally different from using the containers' default comparison operators (default behavior). |
Warning | |
---|---|
this is not an order relationship on containers. As a side effect, it is possible to have BOOST_TEST(c_a == c_b) and BOOST_TEST(c_a != c_b) failing at the same time |
Sequences are compared using the specified operator op
,
evaluated on the left and right elements of the respective sequences. The
order of the compared elements is given by the iterators of the respective
containers [13]. In case of failure, the indices of the elements failing op
are returned.
Code |
---|
#define BOOST_TEST_MODULE boost_test_sequence_per_element #include <boost/test/included/unit_test.hpp> #include <vector> #include <list> namespace tt = boost::test_tools; BOOST_AUTO_TEST_CASE( test_sequence_per_element ) { std::vector<int> a{1,2,3}; std::vector<long> b{1,5,3}; std::list<short> c{1,5,3,4}; BOOST_TEST(a == b, tt::per_element()); // nok: a[1] != b[1] BOOST_TEST(a != b, tt::per_element()); // nok: a[0] == b[0] ... BOOST_TEST(a <= b, tt::per_element()); // ok BOOST_TEST(b < c, tt::per_element()); // nok: size mismatch BOOST_TEST(b >= c, tt::per_element()); // nok: size mismatch BOOST_TEST(b != c, tt::per_element()); // nok: size mismatch } |
Output |
---|
> ./boost_test_sequence_per_element Running 1 test case... test.cpp(21): error: in "test_sequence_per_element": check a == b has failed Mismatch at position 1: 2 != 5. test.cpp(23): error: in "test_sequence_per_element": check a != b has failed Mismatch at position 0: 1 == 1. Mismatch at position 2: 3 == 3. test.cpp(25): error: in "test_sequence_per_element": check b < c has failed Collections size mismatch: 3 != 4 test.cpp(26): error: in "test_sequence_per_element": check b >= c has failed Collections size mismatch: 3 != 4 test.cpp(27): error: in "test_sequence_per_element": check b != c has failed Collections size mismatch: 3 != 4 *** 5 failures are detected in the test module "boost_test_sequence_per_element" |
For the sequences to be comparable element-wise, the following conditions should be met:
op
should be one of
the comparison operator ==
,
!=
, <
,
<=
, >
,
>=
a_i op
b_i
should be defined, where
the type of a_i
and
b_i
are the type returned
by the dereference operator of the respective collections.
Caution | |
---|---|
the resulting type of " BOOST_TEST(c_a == c_b == 42, boost::test_tools::per_element() ); // does not compile |
By specifying the manipulator boost::test_tools::lexicographic
,
the containers are compared using the lexicographical
order and for which the Unit Test Framework provides
additional diagnostic in case of failure.
BOOST_TEST(c_a op c_b, boost::test_tools::lexicographic() );
The comparison is performed in the order given by forward iterators of the containers.
Tip | |
---|---|
lexicographic comparison yields a total order on the containers: the
statements |
Code |
---|
#define BOOST_TEST_MODULE boost_test_container_lex #include <boost/test/included/unit_test.hpp> #include <vector> namespace tt = boost::test_tools; BOOST_AUTO_TEST_CASE( test_collections_vectors_lex ) { std::vector<int> a{1,2,3}, b{1,2,2}, c{1,2,3,4}; BOOST_TEST(a < a, tt::lexicographic()); BOOST_TEST(a < b, tt::lexicographic()); BOOST_TEST(a < c, tt::lexicographic()); BOOST_TEST(a >= c, tt::lexicographic()); // does not compile //BOOST_TEST(a == c, tt::lexicographic()); //BOOST_TEST(a != c, tt::lexicographic()); } |
Output |
---|
> ./boost_test_container_lex --log_level=all Running 1 test case... Entering test module "boost_test_container_lex" test.cpp(15): Entering test case "test_collections_vectors_lex" test.cpp(19): error: in "test_collections_vectors_lex": check a < a has failed Collections appear to be equal. test.cpp(20): error: in "test_collections_vectors_lex": check a < b has failed Failure at position 2: 3 >= 2. test.cpp(21): info: check a < c has passed test.cpp(22): error: in "test_collections_vectors_lex": check a >= c has failed Second collection has extra trailing elements. test.cpp(15): Leaving test case "test_collections_vectors_lex"; testing time: 267us Leaving test module "boost_test_container_lex"; testing time: 341us *** 3 failures are detected in the test module "boost_test_container_lex" |
op
should be one of
the ordered comparison operator <
,
<=
, >
,
>=
A sequence is given by the iteration over a forward iterable container. A forward iterable container is a container (C++11):
size
and begin
, as well
as the fields const_iterator
and value_type
value_type
is not of type char
or
wchar_t
To that respect, C-arrays are not forward iterable containers:
Code |
---|
#define BOOST_TEST_MODULE boost_test_container_c #include <boost/test/included/unit_test.hpp> #include <sstream> #include <map> #include <vector> BOOST_AUTO_TEST_CASE( test_collections_not_on_c_arrays ) { int a[] = {1, 2, 3}; int b[] = {1, 5, 3, 4}; BOOST_TEST(a == b); } |
Output |
---|
> ./boost_test_macro_container_c_array --log_level=all Running 1 test case... Entering test module "boost_test_container_c" test.cpp(15): Entering test case "test_collections_not_on_c_arrays" test.cpp(19): error: in "test_collections_not_on_c_arrays": check a == b has failed [0x7fff526e5bc4 != 0x7fff526e5bb0] test.cpp(15): Leaving test case "test_collections_not_on_c_arrays"; testing time: 323us Leaving test module "boost_test_container_c"; testing time: 526us *** 1 failure is detected in the test module "boost_test_container_c" |
The detection of the containers is delegated to the class boost::unit_test::is_forward_iterable
,
which for C++11 detects the required member functions and fields. However
for C++03, the types providing the sequences should be explicitly indicated
to the Unit Test Framework by a specialization of
boost::unit_test::is_forward_iterable
[14].
[10]
this might not be the case for e.g. std::unordered_map
,
for which the buckets might be filled differently depending on the
insertion order.
[11]
in this case v_a < v_b
means that the point v_a
is inside the rectangle (origin, v_b
)
[12] either defined by the container or by the user
[13] the containers should yield the same sequences for a fixed set of elements they contain
[14]
Standard containers of the STL
are recognized as collections.