教Google - 测试如何打印特征矩阵 [英] Teach Google-Test how to print Eigen Matrix

查看:196
本文介绍了教Google - 测试如何打印特征矩阵的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介



我使用Google的测试框架Google-Mock对Eigen矩阵写测试,如另一个问题



使用下面的代码,我可以添加一个自定义 Matcher 来匹配Eigen矩阵到给定的精度。

  MATCHER_P2(EigenApproxEqual,expect,prec,
std :: string(negation?is not:is)+approx equal to+
::测试:: PrintToString(expect)+\\\
with precision+
:: testing :: PrintToString(prec)){
return arg.isApprox(expect,prec);
}



这是做什么的是比较两个特征矩阵 isApprox 方法,如果它们不匹配Google-Mock将打印相应的错误消息,其中将包含矩阵的预期值和实际值。或者,至少应该...



问题



采取以下简单测试用例: p>

  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:A $:31:失败
值b $ b预计:大约是相等至32字节的对象< 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-00 F0-3F 00-00 00-00 00-40 00-00 00-00 00-00 08-40>
精密1E-07
实际:32字节的对象< 00-00 00-00 00-00 00-00 00-00 00-00 00-00 00-40 00-00 00-99 00 00-00 F0-3F 00-00 00-00 00-00 08-40>

如您所见,Google-Test打印了矩阵的十六进制转储,而不是更好表示他们的价值观。该谷歌的文档说以下有关自定义类型的打印值:




这打印机知道如何打印内置的C ++类,原生数组,STL
的容器,和任何支持类型的<< ;运算符。对于其他
类型,它打印在价值的原始字节,希望您在
用户自己看着办吧。




本征矩阵配备了一个 运营商的LT;< 。但是,Google-Test或C ++编译器忽略它。据我了解,原因如下:此运算符的签名读取( IO.h(线240)

 模板< 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> &






我的问题



我的问题是以下。我如何告诉编译器优先于Google-test hex-dump的特征 operator< ?假设我不能修改Eigen矩阵的类定义。






我的尝试



到目前为止,我已经尝试过以下操作。



定义函数

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

将不能用于运算符<< / code>不起作用。



我发现唯一的工作是使用Eigen的插件机制



使用文件 eigen_matrix_addons.hpp

  friend PrintTo(const Derived& m,:: std :: ostream * o){
* o<< \\\
< m;
}

和以下include伪指令

  #define EIGEN_MATRIXBASE_PLUGINeigen_matrix_addons.hpp
#include< Eigen / Dense>

测试将产生以下输出:

  gtest_eigen_print.cpp:31:失败
值:A
预期:约等于
0 2
1 3
精确1e-07
实际:
0 1
2 3


$ b b

这有什么问题?



对于Eigen矩阵,这可能是一个可以接受的解决方案。但是,我知道,我将不得不应用相同的东西到其他模板类,很快,不幸的是,不提供像Eigen的插件机制,其定义我不能直接访问。



因此,我的问题是:是否有一种方法指向编译器正确运算符< PrintTo 函数,而不修改类的定义本身?






/ h1>

  #include< Eigen / Dense> 

#include< gtest / gtest.h>
#include< gmock / gmock.h>
#include< gmock / gmock-matchers.h>

//用于Eigen矩阵的GMock匹配器。
MATCHER_P2(EigenApproxEqual,expect,prec,
std :: string(negation?is not: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类型定义了一个trait。使用它,我们可以使用 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内部默认值之间的模糊性。 有没有办法消除歧义并将编译器指向我的函数?

 命名空间Eigen {
//当在
//内部定义Eigen命名空间时,此函数将导致以下编译器错误。
// gmock-1.7.0 / gtest / include / gtest / gtest-printers.h:600:5:错误:
//调用'PrintTo'是不明确的
// PrintTo (value,os);
// ^ ~~~~~~
//
//在全局命名空间中定义时,它将被忽略。
template< class Derived,
class = typename std :: enable_if< is_eigen< Derived> :: value> :: type>
void PrintTo(const Derived& m,:: std :: ostream * o){
* o& \\\
< 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& \\\
< static_cast< const Eigen :: DenseBase< Derived> &>(m);
return o;
}






编辑:回答



在下面的代码中,我通过@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?is not: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& \\\
< m;
}
};

template< class Base>
const EigenPrintWrap< Base> & print_wrap(const Base& base){
return static_cast< const EigenPrintWrap< 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)));
}


解决方案

解决问题。



google测试实现了一个模板函数

 命名空间测试{namespace internal {

template< typename T>
void PrintTo(const T& value,std :: ostream * o){/ * do smth * /}

}}
pre>

Eigen库定义了一个基于派生的打印机函数。因此

  struct EigenBase {}; 
std :: ostream&运算符<< (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的运算符<< 是基于推导的,模板函数也将被重载决议优先考虑。



Eigen可以提供一个继承了运算符<< 的CRTP基类,这是更好的匹配类型。



可以做是从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函数是inline和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;

}
};

//这是神奇的出现,因为oeprators实际上是
//使用与MyEigenSub类匹配的签名来定义。
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<<从CRTP调用
}

TEST(EigenCRTPPrint,t5){
MyEigenSub e;
print_to(e,& std :: cout); // print print from CRTP called
}


Introduction

I am writing tests on Eigen matrices using Google's testing framework Google-Mock, as already discussed in another question.

With the following code I was able to add a custom Matcher to match Eigen matrices to a given precision.

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

What this does is to compare two Eigen matrices by their isApprox method, and if they don't match Google-Mock will print a corresponding error message, which will contain the expected, and the actual values of the matrices. Or, it should at least...

The Problem

Take the following simple test case:

TEST(EigenPrint, Simple) {
    Eigen::Matrix2d A, B;
    A << 0., 1., 2., 3.;
    B << 0., 2., 1., 3.;

    EXPECT_THAT(A, EigenApproxEqual(B, 1e-7));
}

This test will fail because A, and B are not equal. Unfortunately, the corresponding error message looks like this:

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>

As you can see, Google-Test prints a hex-dump of the matrices, instead of a nicer representation of their values. The Google-documentation says the following about printing values of custom types:

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.

The Eigen matrix comes with an operator<<. However, Google-Test, or the C++ compiler, rather, ignores it. To my understanding, for the following reason: The signature of this operator reads (IO.h (line 240))

template<typename Derived>
std::ostream &operator<< (std::ostream &s, const DenseBase<Derived> &m);

I.e. it takes a const DenseBase<Derived>&. The Google-test hex-dump default printer on the other hand is the default implementation of a template function. You can find the implementation here. (Follow the call-tree starting from PrintTo to see that this is the case, or prove me wrong. ;))

So, the Google-Test default printer is a better match because it takes a const Derived &, and not only its base class const DenseBase<Derived> &.


My Question

My question is the following. How can I tell the compiler to prefer the Eigen specific operator << over the Google-test hex-dump? Under the assumption that I cannot modify the class definition of the Eigen matrix.


My Attempts

So far, I've tried the following things.

Defining a function

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

won't work for the same reason for which operator<< doesn't work.

The only thing which I found that worked is to use Eigen's plugin mechanism.

With a file eigen_matrix_addons.hpp:

friend void PrintTo(const Derived &m, ::std::ostream *o) {
    *o << "\n" << m;
}

and the following include directive

#define EIGEN_MATRIXBASE_PLUGIN "eigen_matrix_addons.hpp"
#include <Eigen/Dense>

the test will produce the following output:

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

What's wrong with that?

For Eigen matrices this is probably an acceptable solution. However, I know that I will have to apply the same thing to other template classes, very soon, which unfortunately, do not offer a plugin mechanism like Eigen's, and whose definitions I don't have direct access to.

Hence, my question is: Is there a way to point the compiler to the right operator<<, or PrintTo function, without modifying the class' definition itself?


The Full Code

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


Edit: Further Attempts

I made some progress with an SFINAE approach.

First, I defined a trait for Eigen types. With it we can use std::enable_if to provide template functions only for types that fulfill this trait.

#include <type_traits>
#include <Eigen/Dense>

template <class Derived>
struct is_eigen : public std::is_base_of<Eigen::DenseBase<Derived>, Derived> {
};

My first thought was to provide such a version of PrintTo. Unfortunately, the compiler complains about an ambiguity between this function, and the Google-Test internal default. Is there a way to disambiguate and point the compiler to my function?

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

Another approach is to overload the operator<< for the Eigen type. It does actually work. However, the downside is that it is a global overload of the ostream operator. So, it is impossible to define any test-specific formatting (e.g. the additional new-line) without this change also affecting non-testing code. Hence, I would prefer a specialized PrintTo like the one above.

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


Edit: Following @Alex's Answer

In the following code I implement the solution by @Alex and implement a small function that converts references of Eigen matrices to the printable type.

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

解决方案

The problems you encounter are overload resolution problems.

google test implements a template function

namespace testing { namespace internal {

template <typename T>
void PrintTo(const T& value, std::ostream *o) { /* do smth */ }

} }

The Eigen library defines a printer function that is based on derivation. Hence

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
}

Both do have questionable design.

Google Test should not provide an implementation of PrintTo but should instead check at compile time whether the user provides a PrintTo and otherwise call a different default printing function PrintToDefault. The PrintTo provides is a better match than the one you provided (according to overload resolution).

on the other hand Eigen's operator<< is based on derivation and a template function will also be preferred by overload resolution.

Eigen could provide a CRTP base class that inherits the operator<< which a better matching type.

What you can do is inherit from eigen and provide a CRTP overload to your inherited class avoiding the issue.

#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
}

这篇关于教Google - 测试如何打印特征矩阵的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆