Научите Google-Test печатать Eigen Matrix
Я пишу тесты на матрицах Eigen с использованием среды тестирования Google Google-Mock, как уже обсуждалось вДругой вопрос.
С помощью следующего кода я смог добавить кастомMatcher
сопоставить собственные матрицы с заданной точностью.
MATCHER_P2(EigenApproxEqual, expect, prec,
std::string(negation ? "isn't" : "is") + " approx equal to" +
::testing::PrintToString(expect) + "\nwith precision " +
::testing::PrintToString(prec)) {
return arg.isApprox(expect, prec);
}
Для этого нужно сравнить две собственные матрицы по ихisApprox
методи, если они не совпадают, Google-Mock выведет соответствующее сообщение об ошибке, которое будет содержать ожидаемые и фактические значения матриц. Или, по крайней мере ...
Возьмите следующий простой тестовый пример:
TEST(EigenPrint, Simple) {
Eigen::Matrix2d A, B;
A << 0., 1., 2., 3.;
B << 0., 2., 1., 3.;
EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}
Этот тест не пройден, потому чтоA
, а такжеB
не равны К сожалению, соответствующее сообщение об ошибке выглядит так:
gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 00-40 00-00 00-00 00-00 08-40>
with precision 1e-07
Actual: 32-byte object <00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-40 00-00 00-00 00-00 F0-3F 00-00 00-00 00-00 08-40>
Как вы можете видеть, Google-Test печатает шестнадцатеричный дамп матриц вместо более точного представления их значений.Google-документация говорит следующее о печати значений пользовательских типов:
Этот принтер знает, как печатать встроенные типы C ++, собственные массивы, контейнеры STL илюбой тип, который поддерживает оператор <<, Для других типов он печатает необработанные байты в значении и надеется, что пользователь сможет это выяснить.
Собственная матрица поставляется сoperator<<
, Однако Google-Test или компилятор C ++, скорее, игнорирует его. Насколько я понимаю, по следующей причине: подпись этого оператора читает (IO.h (строка 240))
template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);
То есть это занимаетconst DenseBase<Derived>&
, С другой стороны, принтер по умолчанию с шестнадцатеричным дампом Google-test является реализацией функции шаблона. Вы можете найти реализациюВот, (Следуйте по дереву вызовов, начиная сPrintTo чтобы увидеть, что это так, или доказать, что я не прав. ;))
Таким образом, принтер Google-Test по умолчанию лучше подходит, потому что он требуетconst Derived &
и не только его базовый классconst DenseBase<Derived> &
.
У меня вопрос следующий. Как я могу сказать компилятору, чтобы он предпочел собственныйoperator <<
над гекс-дампом гугл-теста? В предположении, что я не могу изменить определение класса собственной матрицы.
До сих пор я пробовал следующие вещи.
Определение функции
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);
не будет работать по той же причине, по которойoperator<<
не работает
Единственное, что я нашел, это сработало, чтобы использовать Eigen'sплагин механизм.
С файломeigen_matrix_addons.hpp
:
friend void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}
и следующее включает директиву
#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>
тест выдаст следующий результат:
gtest_eigen_print.cpp:31: Failure
Value of: A
Expected: is approx equal to
0 2
1 3
with precision 1e-07
Actual:
0 1
2 3
Что в этом плохого?Для собственных матриц это, вероятно, приемлемое решение. Однако я знаю, что очень скоро мне придется применить то же самое к другим классам шаблонов, которые, к сожалению, не предлагают механизм плагинов, такой как Eigen, и к определениям которого у меня нет прямого доступа.
Следовательно, мой вопрос: есть ли способ указать компилятору вправоoperator<<
, или жеPrintTo
функция, без изменения самого определения класса?
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
// A GMock matcher for Eigen matrices.
MATCHER_P2(EigenApproxEqual, expect, prec,
std::string(negation ? "isn't" : "is") + " approx equal to" +
::testing::PrintToString(expect) + "\nwith precision " +
::testing::PrintToString(prec)) {
return arg.isApprox(expect, prec);
}
TEST(EigenPrint, Simple) {
Eigen::Matrix2d A, B;
A << 0., 1., 2., 3.;
B << 0., 2., 1., 3.;
EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}
Изменить: дальнейшие попыткиЯ добился определенного прогресса в подходе SFINAE.
Сначала я определил черту для собственных типов. С этим мы можем использоватьstd::enable_if
обеспечить шаблонные функции только для типов, которые удовлетворяют этой особенности.
#include <type_traits>
#include <Eigen/Dense>
template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};
Моей первой мыслью было предоставить такую версиюPrintTo
, К сожалению, компилятор жалуется на неоднозначность между этой функцией и внутренним значением по умолчанию Google-Test.Есть ли способ устранить неоднозначность и указать компилятор на мою функцию?
namespace Eigen {
// This function will cause the following compiler error, when defined inside
// the Eigen namespace.
// gmock-1.7.0/gtest/include/gtest/gtest-printers.h:600:5: error:
// call to 'PrintTo' is ambiguous
// PrintTo(value, os);
// ^~~~~~~
//
// It will simply be ignore when defined in the global namespace.
template <class Derived,
class = typename std::enable_if<is_eigen<Derived>::value>::type>
void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}
}
Другой подход заключается в перегрузкеoperator<<
для собственного типа. Это действительно работает. Однако недостатком является то, что это глобальная перегрузка оператора ostream. Таким образом, невозможно определить какое-либо специфичное для теста форматирование (например, дополнительную новую строку) без этого изменения, также затрагивающего не тестирующий код. Следовательно, я бы предпочел специализированныйPrintTo
как тот, что выше.
template <class Derived,
class = typename std::enable_if<is_eigen<Derived>::value>::type>
::std::ostream &operator<<(::std::ostream &o, const Derived &m) {
o << "\n" << static_cast<const Eigen::DenseBase<Derived> &>(m);
return o;
}
Изменить: После @ Алекс ответВ следующем коде я реализую решение с помощью @Alex и реализую небольшую функцию, которая преобразует ссылки на собственные матрицы в печатный тип.
#include <Eigen/Dense>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
MATCHER_P(EigenEqual, expect,
std::string(negation ? "isn't" : "is") + " equal to" +
::testing::PrintToString(expect)) {
return arg == expect;
}
template <class Base>
class EigenPrintWrap : public Base {
friend void PrintTo(const EigenPrintWrap &m, ::std::ostream *o) {
*o << "\n" << m;
}
};
template <class Base>
const EigenPrintWrap<Base> &print_wrap(const Base &base) {
return static_cast<const EigenPrintWrap<Base> &>(base);
}
TEST(Eigen, Matrix) {
Eigen::Matrix2i A, B;
A << 1, 2,
3, 4;
B = A.transpose();
EXPECT_THAT(print_wrap(A), EigenEqual(print_wrap(B)));
}