如何将 BOOST_CHECK_CLOSE 用于用户定义的类型

How to use BOOST_CHECK_CLOSE for User defined types

我想检查以下类型的对象是否符合预期值almost/close

class MyTypeWithDouble
{
    public:
      MyTypeWithDouble(double); 
      bool operator == (const MyTypeWithDouble& rhs) const; //Checks for Equality
    private:
      double m; 
};

/////////////////////////////////////////

class MyTypeWithVector
{
    public:
      MyTypeWithVector(std::vector<double>v);
      bool operator == (const MyTypeWithVector& rhs) const; //Checks for Equality 
    private:
      std::vector<double> v;
};

所以单元测试看起来像这样

/// TEST1 ////////////////
MyTypeWithDouble t1(42.100001);
BOOST_CHECK_CLOSE(t1,42.1,0.1); 

//////TEST2//////////////////////////////


std::vector<double> v; //no initalizer do not have c++11 :-(
v.push_back(42.1); 
MyTypeWithVector t2(v);
std::vector<double> compare;
compare.push_back(42.100001);

MY_OWN_FUNCTION_USING_BOOST(t2,compare,0.1); //There is only   BOOST_CHECK_EQUAL_COLLECTION available for collections

谢谢,ToBe

我认为你设计过度了。我会建议一个简单的宏,也许有一个合适的朋友定义。话虽如此,让我们接受挑战吧。


必要的调整

你的类型应该

  • default-constructible 用于 check_is_close_t 实施。
  • 此外,必须 有一种获取值的方法,并且由于您拒绝创建 getter,剩下的唯一选择是声明一个访问器class 作为好友

我们得到

class MyTypeWithDouble
{
    public:
      constexpr MyTypeWithDouble(double v = 0) : m(v) {}
      MyTypeWithDouble& operator=(MyTypeWithDouble const&) noexcept = default;
      constexpr MyTypeWithDouble(MyTypeWithDouble const&) noexcept = default;
    private:
      friend class unittest_op::access;
      double m;
};

通过一些乏味的工作(在 header?),我们可以利用这个 access 漏洞来实现其他一切。 如何? 好吧,我们定义了一个 "getter",好吧,但是 在 class 定义之外

我在 access 中定义了一个特征 class 模板(所以它隐含了一个 friend),你可以专门针对你的 "floating-point-like" 类型:

namespace unittest_op {
    template<> class access::impl<MyTypeWithDouble> {
      public:
        typedef double result_type;
        static result_type call(MyTypeWithDouble const& v) { return v.m; }
    };
}

仅此而已。好吧,这就是 作为 type/test 实施者的全部内容。当然,我们还需要完成这项工作。


基本要素

存在 unittest_op 命名空间的唯一原因是定义 "relaying" 知道如何访问自定义类型中包含的值的运算符。

请注意我们如何

  • 不需要向您的 user-defined 类型添加任何内容
  • 我们也得到混合操作数(例如2 * MyTypeWithDouble(7.0) -> MyTypeWithDouble(14.0)
  • 并且我们还定义了 operator<<,因此断言宏知道如何打印 MyTypeWithDouble
  • 的值

感谢工作并不复杂:

namespace unittest_op {
    class access {
        template<typename T, typename Enable = void> class impl;

        template<typename T>
            class impl<T, typename std::enable_if<std::is_arithmetic<T>::value, void>::type>
        {
          public: typedef T result_type;
            static T            & call(T& v) { return v; }
            static T const& call(T const& v) { return v; }
        };

      public:
        template<typename T>
        static typename impl<T>::result_type do_access(T const& v) { return impl<T>::call(v); }

        template<typename T> static constexpr bool can_access(decltype(do_access(std::declval<T>()))*) { return true; }
        template<typename T> static constexpr bool can_access(...) { return false; }
    };

    template<typename T>
        typename std::enable_if<access::can_access<T>(nullptr) && not std::is_arithmetic<T>::value, std::ostream&>::type
            operator<<(std::ostream& os, T const& v) { return os << "{" << access::do_access(v) << "}"; }

    template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
        static T operator-(T const& lhs) { return - access::do_access(lhs); }

    template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
        static T operator+(T const& lhs) { return + access::do_access(lhs); }

#define UNITTEST_OP_BINOP(OP) \
    template <typename T1, typename T2> \
        static decltype(access::do_access(std::declval<T1>()) OP access::do_access(std::declval<T2>()))  \
            operator OP(T1 const& lhs, T2 const& rhs) { return access::do_access(lhs) OP access::do_access(rhs); } \
    using ::unittest_op::operator OP;

    UNITTEST_OP_BINOP(==)
    UNITTEST_OP_BINOP(!=)

    UNITTEST_OP_BINOP(< )
    UNITTEST_OP_BINOP(> )
    UNITTEST_OP_BINOP(<=)
    UNITTEST_OP_BINOP(>=)

    UNITTEST_OP_BINOP(+ )
    UNITTEST_OP_BINOP(- )
    UNITTEST_OP_BINOP(% )
    UNITTEST_OP_BINOP(* )
    UNITTEST_OP_BINOP(/ )

    // assign-ops only for lvalue types (i.e. identity `access::impl<T>`)
    UNITTEST_OP_BINOP(+=)
    UNITTEST_OP_BINOP(-=)
    UNITTEST_OP_BINOP(%=)
    UNITTEST_OP_BINOP(*=)
    UNITTEST_OP_BINOP(/=)

#undef UNITTEST_OP_BINOP
}

请注意,这些都是 "open" 模板,我们已采取必要的预防措施以确保这些运算符 适用,前提是 do_access 是定义 该类型不是算术类型。

为什么要采取这些预防措施?

嗯。我们要做一个 power-move:我们将把运算符重载注入 boost::test_tools 命名空间,以便 BOOST_CHECK* 宏实现可以找到它们。

如果我们采取刚才提到的预防措施,我们会因我们不关心的类型的模糊运算符重载而引发很多问题。


夺权

权力获取很简单:我们在 boost::test_tools 命名空间中注入 (using) 我们的每个操作符模板。

现在我们可以开始了:

Live On Coliru

BOOST_AUTO_TEST_CASE(my_test)
{
    MyTypeWithDouble v(4);

    BOOST_CHECK_CLOSE(3.99, v, MyTypeWithDouble(0.1));
}

版画

Running 2 test cases...
main.cpp(117): error in "my_test": difference{0.25%} between 3.99{3.9900000000000002} and v{{4}} exceeds {0.10000000000000001}%

完整节目

Live On Coliru

#include <utility>
#include <type_traits>
#include <iostream>

namespace unittest_op {
    class access {
        template<typename T, typename Enable = void> class impl;

        template<typename T>
            class impl<T, typename std::enable_if<std::is_arithmetic<T>::value, void>::type>
        {
          public: typedef T result_type;
            static T            & call(T& v) { return v; }
            static T const& call(T const& v) { return v; }
        };

      public:
        template<typename T>
        static typename impl<T>::result_type do_access(T const& v) { return impl<T>::call(v); }

        template<typename T> static constexpr bool can_access(decltype(do_access(std::declval<T>()))*) { return true; }
        template<typename T> static constexpr bool can_access(...) { return false; }
    };

    template<typename T>
        typename std::enable_if<access::can_access<T>(nullptr) && not std::is_arithmetic<T>::value, std::ostream&>::type
            operator<<(std::ostream& os, T const& v) { return os << "{" << access::do_access(v) << "}"; }

    template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
        static T operator-(T const& lhs) { return - access::do_access(lhs); }

    template <typename T, typename Enable=decltype(access::do_access(std::declval<T>())) >
        static T operator+(T const& lhs) { return + access::do_access(lhs); }

#define UNITTEST_OP_BINOP(OP) \
    template <typename T1, typename T2> \
        static decltype(access::do_access(std::declval<T1>()) OP access::do_access(std::declval<T2>()))  \
            operator OP(T1 const& lhs, T2 const& rhs) { return access::do_access(lhs) OP access::do_access(rhs); } \
    using ::unittest_op::operator OP;

    UNITTEST_OP_BINOP(==)
    UNITTEST_OP_BINOP(!=)

    UNITTEST_OP_BINOP(< )
    UNITTEST_OP_BINOP(> )
    UNITTEST_OP_BINOP(<=)
    UNITTEST_OP_BINOP(>=)

    UNITTEST_OP_BINOP(+ )
    UNITTEST_OP_BINOP(- )
    UNITTEST_OP_BINOP(% )
    UNITTEST_OP_BINOP(* )
    UNITTEST_OP_BINOP(/ )

    // assign-ops only for lvalue types (i.e. identity `access::impl<T>`)
    UNITTEST_OP_BINOP(+=)
    UNITTEST_OP_BINOP(-=)
    UNITTEST_OP_BINOP(%=)
    UNITTEST_OP_BINOP(*=)
    UNITTEST_OP_BINOP(/=)

#undef UNITTEST_OP_BINOP
}

namespace boost { namespace test_tools {

    using unittest_op::operator ==;
    using unittest_op::operator !=;

    using unittest_op::operator < ;
    using unittest_op::operator > ;
    using unittest_op::operator <=;
    using unittest_op::operator >=;

    using unittest_op::operator + ;
    using unittest_op::operator - ;
    using unittest_op::operator % ;
    using unittest_op::operator * ;
    using unittest_op::operator / ;

    using unittest_op::operator +=;
    using unittest_op::operator -=;
    using unittest_op::operator %=;
    using unittest_op::operator *=;
    using unittest_op::operator /=;

    using unittest_op::operator <<;

} }

class MyTypeWithDouble
{
    public:
      constexpr MyTypeWithDouble(double v = 0) : m(v) {}
      MyTypeWithDouble& operator=(MyTypeWithDouble const&) noexcept = default;
      constexpr MyTypeWithDouble(MyTypeWithDouble const&) noexcept = default;
    private:
      friend class unittest_op::access;
      double m;
};

namespace unittest_op {
    template<> class access::impl<MyTypeWithDouble> {
      public:
        typedef double result_type;
        static result_type call(MyTypeWithDouble const& v) { return v.m; }
    };
}

#define BOOST_TEST_MODULE MyTest
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(my_test)
{
    MyTypeWithDouble v(4);

    BOOST_CHECK_CLOSE(3.99, v, MyTypeWithDouble(0.1));
}

BOOST_AUTO_TEST_CASE(general_operator_invocations) // just a testbed to see the overloads are found and compile
{
    MyTypeWithDouble v(4);

    using namespace unittest_op; // we're not using the test_tools here

    BOOST_CHECK(4.00000000000000001 == v);
    BOOST_CHECK(4.000000000000001   != v);

#define UNITTEST_OP_BINOP(OP) { \
    auto x = v OP static_cast<MyTypeWithDouble>(0.01); \
    x = static_cast<MyTypeWithDouble>(0.01) OP v; \
    x = v OP v; \
    (void) x; \
}

    UNITTEST_OP_BINOP(==)
    UNITTEST_OP_BINOP(!=)
    UNITTEST_OP_BINOP(+ )
    UNITTEST_OP_BINOP(- )
    //UNITTEST_OP_BINOP(% )
    UNITTEST_OP_BINOP(* )
    UNITTEST_OP_BINOP(/ )

    UNITTEST_OP_BINOP(< )
    UNITTEST_OP_BINOP(> )
    UNITTEST_OP_BINOP(<=)
    UNITTEST_OP_BINOP(>=)

    -v == -v;
    +v == +v;
}