【问题标题】:BOOST_CHECK fails to compile operator<< for custom typesBOOST_CHECK 无法为自定义类型编译 operator<<
【发布时间】:2013-07-10 13:57:27
【问题描述】:

我写了这个非常简单的类,以便清楚我的问题是什么:

class A
{
public:
    int x;
    A(int y) {x=y;}
    bool operator==(const A &other) const {return x==other.x;}
};

现在,如果我定义 A first(1) 和 A second(1),那么 BOOST_CHECK_EQUAL(first, second) 应该通过对我来说似乎很自然。但是,我在尝试执行此操作时遇到 50 个错误,第一个听起来像: no math for operator

【问题讨论】:

  • 错误是什么?
  • 它无法编译...而且我正在使用代码块,因此无法复制粘贴错误。所有这些都在文件 test_tools.hpp 中,我不太了解它们的含义(我在帖子中提到了第一个)。我应该举更多的例子吗?
  • 我用 gcc 编译,我想粘贴错误,但是 11400 个字符太长了,无法评论。
  • 根据this reference BOOST_CHECK_EQUAL 将打印不相等参数的值,因此宏扩展为可能包含cout &lt;&lt; first &lt;&lt; " != " &lt;&lt; second 之类的代码。因此你必须为你的类定义一个输出操作符(一个非成员函数std::ostream&amp; operator&lt;&lt;(std::ostream&amp;, const A&amp;))。
  • 听起来很奇怪,大家在使用单元测试的时候都会这样做吗?没有其他的检查方法吗?

标签: c++ unit-testing boost


【解决方案1】:

我已经确定了解决operator&lt;&lt; 问题的三种方法。

第一种方法是为您的类型提供operator&lt;&lt;。这是必需的,因为当boost_check_equal 失败时,它还会通过使用对象调用operator&lt;&lt; 来记录失败。请参阅休息后的详细附录,以了解这是如何实际完成的。这比看起来更难。

第二种方法是不做我刚才提到的日志记录。你可以通过#definineing BOOST_TEST_DONT_PRINT_LOG_VALUE 来做到这一点。要仅对一项测试禁用日志记录,您可以用 #define 包围有问题的测试,然后立即使用 #undef 它:

#define BOOST_TEST_DONT_PRINT_LOG_VALUE
BOOST_CHECK_EQUAL (first, second);
#undef BOOST_TEST_DONT_PRINT_LOG_VALUE

第三种方法是通过不将一个项目与另一个项目进行比较,而只是检查一个布尔值,从而回避对与您的类型一起使用的 operator&lt;&lt; 的需求:

BOOST_CHECK (first == second);

选择您喜欢的方法。


我的偏好是第一个,但实施起来非常具有挑战性。如果您只是在全局范围内定义 operator&lt;&lt;,它将无法正常工作。我认为这是因为名称解析存在问题。解决此问题的一个流行建议是将operator&lt;&lt; 放在std 命名空间中。这有效,至少在某些编译器的实践中,但我不喜欢它,因为标准禁止向 std 命名空间添加任何内容。

我发现一个更好的方法是为你的类型实现一个自定义的print_log_value 类模板特化。 print_log_value 是 Boost.Test 内部使用的类模板,用于实际调用指定类型的正确 operator&lt;&lt;。它委托operator&lt;&lt; 完成繁重的工作。 Boost [需要引用] 正式支持为您的自定义类型专门化 print_log_value,并因此完成。

假设你的类型叫做Timestamp(在我的代码中),首先为Timestamp定义一个全局的免费operator&lt;&lt;

static inline std::ostream& operator<< (std::ostream& os, const Mdi::Timestamp& ts)
{
    os << "Timestamp";
    return os;
}   

...然后为其提供 print_log_value 特化,委托给您刚刚定义的 operator&lt;&lt;

namespace boost { namespace test_tools {
template<>           
struct print_log_value<Mdi::Timestamp > {
void operator()( std::ostream& os,
    Mdi::Timestamp const& ts)
{
    ::operator<<(os,ts);
}
};                                                          
}}

【讨论】:

  • 我明白了,谢谢,我先做一个简单的boost_check(),因为我确实需要非常紧急地测试一些东西,但我会考虑其他方法。跨度>
  • 至少从 boost_1.61.00 开始,您必须在 boost::test_tools::tt_detail 中指定 print_log_value 模板。
  • 感谢您对问题和解决方案的清晰解释。另一个变体是您可以简单地将operator&lt;&lt; 添加到boost::test_tools::tt_detail 命名空间,而不是专门化print_log_value。请参阅下面的my answer,了解更多详细信息以及正确的格式。
  • 可能是我的Boost版本,不过我发现BOOST_CHECK(a == b);还是需要operator&lt;&lt;,不过你可以骗它配合BOOST_CHECK((a == b))
【解决方案2】:

根据John Dibling 的回答,我正在寻找一种以十六进制而不是十进制转储整数值的方法,我想出了这种方法:

// test_macros.h in my project
namespace myproject
{
namespace test
{
namespace macros
{
    extern bool hex;

    // context manager
    struct use_hex
    {
        use_hex()  { hex = true; }
        ~use_hex() { hex = false; }
    };

 }; // namespace
 }; // namespace
 }; // namespace

namespace boost
{
namespace test_tools
{

    // boost 1.56+ uses these methods

    template<>
    inline                                               
    void                                                 
    print_log_value<uint64>::                       
    operator()(std::ostream & out, const uint64 & t) 
    {                                                    
        if(myproject::test::macros::hex)                    
            out << ::boost::format("0x%016X") % t;           
        else 
            out << t;                                        
    }                                                    

namespace tt_detail
{

    // Boost < 1.56 uses these methods

    template <>
    inline
    std::ostream &
    operator<<(std::ostream & ostr, print_helper_t<uint64> const & ph )
    {
        if(myproject::test::macros::hex)
            return ostr << ::boost::format("0x%016X") % ph.m_t;

        return ostr << ph.m_t;
    }

}; // namespace
}; // namespace
}; // namespace

现在在我的单元测试用例中,我可以通过设置全局静态布尔值来打开/关闭十六进制,例如:

for(uint64 i = 1; i <= 256/64; ++i)
{
    if(i % 2 == 0) test::macros::hex = true;
    else           test::macros::hex = false;
    BOOST_CHECK_EQUAL(i+1, dst.pop());
}

我得到了我正在寻找的行为:

test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [2 != 257]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [0x0000000000000003 != 0x0000000000000102]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [4 != 259]
test_foobar.cc(106): error in "test_foobar_51": check i+1 == dst.pop() failed [0x0000000000000005 != 0x0000000000000104]

或者,我可以使用上下文管理器:

{
    test::macros::use_hex context;

    for(uint64 i = 1; i <= 4; ++i)
    {
        BOOST_CHECK_EQUAL(i + 0x200, i + 0x100);
    }
}

for(uint64 i = 1; i <= 4; ++i)
{
    BOOST_CHECK_EQUAL(i + 0x200, i + 0x100);
}

并且十六进制输出只会在那个块中使用:

test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000201 != 0x0000000000000101]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000202 != 0x0000000000000102]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000203 != 0x0000000000000103]
test_foobar.cc(94): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [0x0000000000000204 != 0x0000000000000104]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [513 != 257]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [514 != 258]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [515 != 259]
test_foobar.cc(100): error in "test_foobar_28": check i + 0x200 == i + 0x100 failed [516 != 260]

【讨论】:

  • +1:现在才看到,我是粉丝。在尝试转储 UDP 数据包之类的东西时,这样的东西非常方便。
【解决方案3】:

这是对优秀answer from John Dibling的补充。问题似乎是在正确的命名空间中需要有一个输出运算符。因此,如果您定义了一个全局输出 operator&lt;&lt;,那么您可以通过在转发给全局运算符的 boost::test_tools::tt_detail 命名空间中定义另一个错误来避免此错误(至少在 Visual Studio 2015,aka vc14 和 boost 1.60 中)。这一微小的调整使人们能够避免print_log_value 类的奇怪和更冗长的专业化。这是我所做的:

namespace boost {
namespace test_tools {
namespace tt_detail {
std::ostream& operator<<(std::ostream& os, Mdi::Timestamp const& ts)
{
    return ::operator<<(os, ts);
}
}  // namespace tt_detail
}  // namespace test_tools
}  // namespace boost

虽然这个问题和答案发布已经三年了,但我还没有看到Boost.Test documentation 中明确讨论过这个问题。

【讨论】:

  • 导入操作符不是更好吗? namespace boost::test_tools::tt_detail { using ::operator&lt;&lt;;}?
  • 当然,如果可行的话。您的解决方案更简洁。
【解决方案4】:

启动 Boost 1.64 有一种简洁的方法可以通过自定义点记录用户定义的类型。此功能的完整文档可以在here 找到。

下面给出了文档中的一个示例。想法是为您要打印的类型定义函数boost_test_print_type,并将此函数带入测试用例(通过ADL找到):

#define BOOST_TEST_MODULE logger-customization-point
#include <boost/test/included/unit_test.hpp>

namespace user_defined_namespace {
  struct user_defined_type {
      int value;

      user_defined_type(int value_) : value(value_)
      {}

      bool operator==(int right) const {
          return right == value;
      }
  };
}

namespace user_defined_namespace {
  std::ostream& boost_test_print_type(std::ostream& ostr, user_defined_type const& right) {
      ostr << "** value of user_defined_type is " << right.value << " **";
      return ostr;
  }
}

BOOST_AUTO_TEST_CASE(test1)
{
    user_defined_namespace::user_defined_type t(10);
    BOOST_TEST(t == 11);

    using namespace user_defined_namespace;
    user_defined_type t2(11);
    BOOST_TEST(t2 == 11);
}

【讨论】:

  • 这对我有用,但我不必定义 BOOST_TEST_MODULE 变量。我也已经在全局范围内定义了我的 operator
猜你喜欢
  • 1970-01-01
  • 2020-09-20
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 2020-12-06
  • 2017-03-20
  • 1970-01-01
相关资源
最近更新 更多