介绍
我正在使用 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
方法比较两个 Eigen 矩阵,如果它们不匹配,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 打印矩阵的十六进制转储,而不是更好地表示它们的值。谷歌文档对自定义类型的打印值进行了以下说明:
这台打印机知道如何打印内置 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 hex-dump 默认打印机是模板函数的默认实现。你可以在这里找到实现。(跟随从PrintTo开始的调用树,看看情况是否如此,或者证明我错了。;))
因此,Google-Test 默认打印机是一个更好的匹配,因为它需要一个const Derived &
,而不仅仅是它的基类const DenseBase<Derived> &
。
我的问题
我的问题如下。我如何告诉编译器更喜欢 Eigen 特定operator <<
于 Google-test hex-dump?假设我不能修改 Eigen 矩阵的类定义。
我的尝试
到目前为止,我已经尝试了以下事情。
定义一个函数
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);
不会因为不起作用的相同原因operator<<
而起作用。
我发现唯一有效的是使用 Eigen 的插件机制。
有一个文件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 方法上取得了一些进展。
首先,我为 Eigen 类型定义了一个特征。有了它,我们可以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<<
Eigen 类型。它确实有效。但是,缺点是它是 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的回答
在下面的代码中,我通过 @Alex 实现了解决方案,并实现了一个小函数,将 Eigen 矩阵的引用转换为可打印类型。
#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)));
}