我正在使用Google的测试框架Google-Mock编写关于Eigen矩阵的测试,正如another question中已经讨论过的那样。
使用以下代码,我可以添加自定义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
method,如果它们不匹配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-documentation说明了有关打印自定义类型值的信息:
本打印机知道如何打印内置C ++类型,本机数组,STL 容器和支持&lt;&lt;的任何类型操作即可。对于其他 类型,它打印值中的原始字节,希望你 用户可以搞清楚。
特征矩阵带有operator<<
。但是,Google-Test或C ++编译器忽略了它。据我所知,由于以下原因:此运算符的签名为(IO.h (line 240))
template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);
即。需要const DenseBase<Derived>&
。另一方面,Google测试hex-dump默认打印机是模板功能的默认实现。您可以找到实施here。 (按照从PrintTo开始的调用树来查看是这种情况,或者证明我错了。;))
因此,Google-Test默认打印机更匹配,因为它需要const Derived &
,而不仅仅是其基类const DenseBase<Derived> &
。
我的问题如下。如何告诉编译器更喜欢特定于特定的operator <<
而不是Google测试的十六进制转储?假设我无法修改特征矩阵的类定义。
到目前为止,我已尝试过以下事项。
定义一个功能
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);
由于operator<<
无法正常工作,无法工作。
我发现唯一有效的方法是使用Eigen&#39; plugin mechanism。
使用文件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
函数,而无需修改类&#39;定义本身?
#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<<
。它确实有效。然而,缺点是它是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)));
}
答案 0 :(得分:6)
您遇到的问题是重载解决问题。
google test实现了模板功能
namespace testing { namespace internal {
template <typename T>
void PrintTo(const T& value, std::ostream *o) { /* do smth */ }
} }
Eigen库定义了一个基于派生的打印机功能。因此
struct EigenBase { };
std::ostream& operator<< (std::ostream& stream, const EigenBase& m) { /* do smth */ }
struct Eigen : public EigenBase { };
void f1() {
Eigen e;
std::cout << e; // works
}
void f2() {
Eigen e;
print_to(eigen, &std::cout); // works
}
两者都有可疑的设计。
Google Test不应提供PrintTo
的实现,而应在编译时检查用户是否提供PrintTo
,否则调用其他默认打印函数PrintToDefault
。 PrintTo
提供的匹配比您提供的匹配更好(根据重载分辨率)。
另一方面,Eigen的operator<<
基于推导,模板函数也是重载决策的首选。
Eigen可以提供一个CRTP基类,它继承了operator<<
更好的匹配类型。
你可以做的是从eigen继承并为你继承的类提供CRTP重载,避免这个问题。
#include <gtest/gtest.h>
#include <iostream>
class EigenBase {
};
std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
o << "operator<< EigenBase called";
return o;
}
template <typename T>
void print_to(const T &t, std::ostream *o) {
*o << "Google Print To Called";
}
class EigenSub : public EigenBase {};
template <typename T>
struct StreamBase {
typedef T value_type;
// friend function is inline and static
friend std::ostream &operator<<(std::ostream &o, const value_type &r) {
o << "operator<< from CRTP called";
return o;
}
friend void print_to(const value_type &t, std::ostream *o) {
*o << "print_to from CRTP called";
}
};
// this is were the magic appears, because the oeprators are actually
// defined with signatures matching the MyEigenSub class.
class MyEigenSub : public EigenSub, public StreamBase<MyEigenSub> {
};
TEST(EigenBasePrint, t1) {
EigenBase e;
std::cout << e << std::endl; // works
}
TEST(EigenBasePrint, t2) {
EigenBase e;
print_to(e, &std::cout); // works
}
TEST(EigenSubPrint, t3) {
EigenSub e;
std::cout << e << std::endl; // works
}
TEST(EigenCRTPPrint, t4) {
MyEigenSub e;
std::cout << e << std::endl; // operator<< from CRTP called
}
TEST(EigenCRTPPrint, t5) {
MyEigenSub e;
print_to(e, &std::cout); // prints print_to from CRTP called
}
答案 1 :(得分:1)
考虑到OP的答案,我想做一些澄清。 与OP的派生解决方案不同,我实际上想要修饰类而不是在断言中使用函数包装器。
为了简单起见而不是使用谷歌测试匹配谓词,我重载了operator==
。
我们不使用Eigen类本身,而是使用一个完全替代Eigen的包装器。因此,每当我们创建Eigen
的实例时,我们都会创建WrapEigen
的实例。
因为我们不打算改变Eigen
派生的实现,所以很好。
此外,我们希望向包装器添加功能。我在这里执行此操作,具有类似StreamBase
和EqualBase
类的仿函数的多重继承。我们在这些仿函数中使用CRTP来获得正确的签名。
为了节省潜在的输入,我在Wrapper
中使用了一个可变参数模板构造函数。它调用相应的基础构造函数(如果存在)。
#include <gtest/gtest.h>
#include <iostream>
#include <utility>
using namespace testing::internal;
struct EigenBase {
explicit EigenBase(int i) : priv_(i) {}
friend std::ostream &operator<<(std::ostream &o, const EigenBase &r) {
o << r.priv_;
return o;
}
friend bool operator==(const EigenBase& a, const EigenBase& b) {
return a.priv_ == b.priv_;
}
int priv_;
};
struct Eigen : public EigenBase {
explicit Eigen(int i) : EigenBase(i) {}
};
template <typename T, typename U>
struct StreamBase {
typedef T value_type;
typedef const value_type &const_reference;
friend void PrintTo(const value_type &t, std::ostream *o) {
*o << static_cast<const U&>(t);
}
};
template <typename T, typename U>
struct EqualBase {
typedef T value_type;
typedef const T &const_reference;
friend bool operator==(const_reference a, const_reference b) {
return static_cast<const U&>(a)
== static_cast<const U&>(b);
}
};
template <typename T, typename U>
struct Wrapper
: public T,
public StreamBase<Wrapper<T,U>, U>,
public EqualBase<Wrapper<T,U>, U> {
template <typename... Args>
Wrapper(Args&&... args) : T(std::forward<Args>(args)...) { }
};
TEST(EigenPrint, t1) {
Eigen e(10);
Eigen f(11);
ASSERT_EQ(e,f); // calls gtest::PrintTo
}
TEST(WrapEigenPrint, t1) {
typedef Wrapper<Eigen, EigenBase> WrapEigen;
WrapEigen e(10);
WrapEigen f(11);
ASSERT_EQ(e,f); // calls our own.
}
答案 2 :(得分:0)
我觉得有必要提供一个我认为比其他人更简单和更好的新答案,尽管它很简单,我可能错过了一些东西。它与你已经尝试过的解决方案非常相似,但它们并不完全相同。
基本上,您不必跳过修改类的插件箍。需要注意的是,您必须为每种类型(PrintTo
,Matrix2d
等)定义Matrix3d
函数;功能模板不起作用。但由于这是一个单元测试,我假设你知道你的所有类型是什么,所以这不是问题。
所以基本上从插件中获取代码并将其放入单元测试中,就像您尝试使用模板化的SFINAE一样:
namespace Eigen
{
void PrintTo(const Matrix2d &m, std::ostream *os)
{
*os << std::endl << m << std::endl;
}
}
没什么特别的。这对我有用,应根据您的测试用例和问题做您想做的事。