How to compare vectors with Boost.Test?
Asked Answered
J

5

30

I am using Boost Test to unit test some C++ code.

I have a vector of values that I need to compare with expected results, but I don't want to manually check the values in a loop:

BOOST_REQUIRE_EQUAL(values.size(), expected.size());

for( int i = 0; i < size; ++i )
{
    BOOST_CHECK_EQUAL(values[i], expected[i]);
}

The main problem is that the loop check doesn't print the index, so it requires some searching to find the mismatch.

I could use std::equal or std::mismatch on the two vectors, but that will require a lot of boilerplate as well.

Is there a cleaner way to do this?

Jura answered 22/10, 2010 at 18:5 Comment(0)
J
38

Use BOOST_CHECK_EQUAL_COLLECTIONS. It's a macro in test_tools.hpp that takes two pairs of iterators:

BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), 
                              expected.begin(), expected.end());

It will report the indexes and the values that mismatch. If the sizes don't match, it will report that as well (and won't just run off the end of the vector).


Note that if you want to use BOOST_CHECK_EQUAL or BOOST_CHECK_EQUAL_COLLECTIONS with non-POD types, you will need to implement

bool YourType::operator!=(const YourType &rhs)  //  or OtherType
std::ostream &operator<<(std::ostream &os, const YourType &yt)

for the comparison and logging, respectively.
The order of the iterators passed to BOOST_CHECK_EQUAL_COLLECTIONS determines which is the RHS and LHS of the != comparison - the first iterator range will be the LHS in the comparisons.

Jura answered 22/10, 2010 at 18:8 Comment(4)
Yeah, I'm busy editing away my shame. :) Thanks for your examples in your answer.Jura
It's a bit too intrusive to force the system under test to implement operator!= and operator<< just to satisfy Boost.Test, IMO. Also, you can't define these member functions for std::vector. See my answer below for a better solution.Flicker
If the output is only used by the unit test, I always add the operators to a common test file rather than to the system itself.Jura
Yes, adding them to the test code is the best idea. I read your answer above as adding them as member functions. In my answer I show how to do this using free functions, not member functions. If they were member functions, you'd have to modify the system under test just to make Boost.Test happy. Also, the restriction is more than just non-POD types because Boost.Test can't figure out how to print a user's struct any more than it can figure out how to print std::vector.Flicker
G
20

Since Boost 1.59 it is much easier to compare std::vector instances. See this documentation for version 1.63 (which is nearly equal in this respect to 1.59).

For example if you have declared std::vector<int> a, b; you can write

BOOST_TEST(a == b);

to get a very basic comparison. The downside of this is that in case of failure Boost only tells you that a and b are not the same. But you get more info by comparing element-wise which is possible in an elegant way

BOOST_TEST(a == b, boost::test_tools::per_element() );

Or if you want a lexicographic comparison you may do

BOOST_TEST(a <= b, boost::test_tools::lexicographic() );
Genoese answered 29/3, 2017 at 11:22 Comment(2)
Excellent, thanks! I'm also excited to see BOOST_TEST_CONTEXT, which appears to have been added in 1.59: boost.org/doc/libs/1_59_0/libs/test/doc/html/boost_test/…Jura
This is the correct answer as of now. Both answers with more votes than this one are needlessly complicated for anyone running any boost version released in the last 7 yearsFelicidad
C
18

A bit off-topic, however, when sometimes one needs to compare collections of floating-point numbers using comparison with tolerance then this snippet may be of use:

// Have to make it a macro so that it reports exact line numbers when checks fail.
#define CHECK_CLOSE_COLLECTION(aa, bb, tolerance) { \
    using std::distance; \
    using std::begin; \
    using std::end; \
    auto a = begin(aa), ae = end(aa); \
    auto b = begin(bb); \
    BOOST_REQUIRE_EQUAL(distance(a, ae), distance(b, end(bb))); \
    for(; a != ae; ++a, ++b) { \
        BOOST_CHECK_CLOSE(*a, *b, tolerance); \
    } \
}

This does not print the array indexes of mismatching elements, but it does print the mismatching values with high precision, so that they are often easy to find.

Example usage:

auto mctr = pad.mctr();
std::cout << "mctr: " << io::as_array(mctr) << '\n';
auto expected_mctr{122.78731602430344,-13.562000155448914};
CHECK_CLOSE_COLLECTION(mctr, expected_mctr, 0.001);
Curren answered 6/7, 2013 at 13:27 Comment(1)
For using it with Boost::Ublas::Vectors you need to adjust the macro, but the idea is good.Offish
I
10

How about BOOST_CHECK_EQUAL_COLLECTIONS?

BOOST_AUTO_TEST_CASE( test )
{
    int col1 [] = { 1, 2, 3, 4, 5, 6, 7 };
    int col2 [] = { 1, 2, 4, 4, 5, 7, 7 };

    BOOST_CHECK_EQUAL_COLLECTIONS( col1, col1+7, col2, col2+7 );
}

example

Running 1 test case...

test.cpp(11): error in "test": check { col1, col1+7 } == { col2, col2+7 } failed.

Mismatch in a position 2: 3 != 4

Mismatch in a position 5: 6 != 7

* 1 failure detected in test suite "example"

Istria answered 22/10, 2010 at 18:10 Comment(0)
F
5

You can use BOOST_REQUIRE_EQUAL_COLLECTIONS with std::vector<T>, but you have to teach Boost.Test how to print a std::vector when you have a vector of vectors or a map whose values are vectors. When you have a map, Boost.Test needs to be taught how to print std::pair. Since you can't change the definition of std::vector or std::pair, you have to do this in such a way that the stream insertion operator you define will be used by Boost.Test without being part of the class definition of std::vector. Also, this technique is useful if you don't want to add stream insertion operators to your system under test just to make Boost.Test happy.

Here is the recipe for any std::vector:

namespace boost
{

// teach Boost.Test how to print std::vector
template <typename T>
inline wrap_stringstream&
operator<<(wrap_stringstream& wrapped, std::vector<T> const& item)
{
    wrapped << '[';
    bool first = true;
    for (auto const& element : item) {
        wrapped << (!first ? "," : "") << element;
        first = false;
    }
    return wrapped << ']';
}

}

This formats the vectors as [e1,e2,e3,...,eN] for a vector with N elements and will work for any number of nested vectors, e.g. where the elements of the vector are also vectors.

Here is the similar recipe for std::pair:

namespace boost
{

// teach Boost.Test how to print std::pair
template <typename K, typename V>
inline wrap_stringstream&
operator<<(wrap_stringstream& wrapped, std::pair<const K, V> const& item)
{
    return wrapped << '<' << item.first << ',' << item.second << '>';
}

}

BOOST_REQUIRE_EQUAL_COLLECTIONS will tell you the index of the mismatched items, as well as the contents of the two collections, assuming the two collections are of the same size. If they are of different sizes, then that is deemed a mismatch and the differing sizes are printed.

Flicker answered 8/8, 2014 at 19:45 Comment(2)
Good point, thanks for mentioning it. I have needed to add similar code to my unit tests several times.Jura
With boost 1.33 one should use wrap_stringstream::wrapped_stream instead of wrap_stringstream, in the code above.Drogin

© 2022 - 2024 — McMap. All rights reserved.