gpt4 book ai didi

c++ - 教Google-Test如何打印特征矩阵

转载 作者:IT老高 更新时间:2023-10-28 22:15:04 24 4
gpt4 key购买 nike

介绍

我正在使用Google的测试框架Google-Mock在 Eigen 矩阵上编写测试,如another question中所述。

使用以下代码,我能够添加自定义Matcher以将 Eigen 矩阵匹配到给定的精度。

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

这样做是根据两个 Eigen 矩阵的 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));
}

该测试将失败,因为 AB不相等。不幸的是,相应的错误消息如下所示:
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关于打印自定义类型的值说明了以下内容:

This printer knows how to print built-in C++ types, native arrays, STL containers, and any type that supports the << operator. For other types, it prints the raw bytes in the value and hopes that you the user can figure it out.



Eigen 矩阵带有 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测试的十六进制转储默认打印机是模板功能的默认实现。您可以找到实现 here。 (按照从 PrintTo开始的调用树,可以看到是这种情况,或者证明我做错了。;)

因此,Google-Test默认打印机是更好的选择,因为它需要一个 const Derived &,而不仅是其基类 const DenseBase<Derived> &

我的问题

我的问题如下。与Google测试的十六进制转储相比,我该如何告诉编译器更喜欢Eigen特定的 operator <<?在我不能修改特征矩阵的类定义的假设下。

我的尝试

到目前为止,我已经尝试了以下方法。

定义功能
template <class Derived>
void PrintTo(const Eigen::DensBase<Derived> &m, std::ostream *o);

由于 operator<<无法正常工作的相同原因而无法正常工作。

我发现唯一有效的方法是使用Eigen的 plugin mechanism

带有文件 eigen_matrix_addons.hpp:
friend void PrintTo(const Derived &m, ::std::ostream *o) {
*o << "\n" << m;
}

和以下include指令
#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 矩阵,这可能是一个可接受的解决方案。但是,我知道我很快将不得不将相同的东西应用于其他模板类,但不幸的是,它们没有提供像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;
}
}

另一种方法是重载 Eigen 类型的 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的答案

在下面的代码中,我实现了@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)));
}

最佳答案

您遇到的问题是过载解析问题。

谷歌测试实现了模板功能

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测试不应该提供 PrintTo的实现,而应该在编译时检查用户是否提供了 PrintTo,否则应调用其他默认的打印函数 PrintToDefaultPrintTo提供的匹配比您提供的匹配更好(根据重载分辨率)。

另一方面,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
}

关于c++ - 教Google-Test如何打印特征矩阵,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25146997/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com