Pergunta

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?

Foi útil?

Solução

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.

Outras dicas

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);

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"

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() );

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top