如何对用户定义的类型使用BOOST_CHECK_CLOSE

时间:2015-01-16 16:02:29

标签: c++ unit-testing boost c++03

如果几乎/接近达到预期值,我想检查以下类型的对象。

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
  • 我不想改变'==运算符的实现 提供Getters。
  • 添加其他运算符功能即可。

谢谢,ToBe

1 个答案:

答案 0 :(得分:5)

我认为你是过度工程的。我建议一个简单的宏,也许有一个合适的朋友定义。那就是说,让我们接受挑战。


必要的调整

你的类型

  • check_is_close_t实现的默认构造。
  • 此外,必须才能获得该值,并且由于您拒绝创建getter,因此唯一的选择是将访问者类声明为朋友

我们得到了

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

通过一些繁琐的工作(在标题中?),我们可以使用这个access漏洞来实现其他一切。 如何?好吧,我们定义了一个“getter”,好吧,但在类定义之外

我在access中定义了一个特质类模板(所以它隐式为friend),你可以专注于你的“类似浮点”的类型:

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

这就是全部。嗯,这就是作为类型/测试实现者的全部内容。当然,我们仍然需要做这项工作。


螺母和螺栓

unittest_op命名空间的唯一原因是定义“中继”操作符,这些操作符知道如何访问自定义类型中包含的值。

注意我们如何

  • 无需向用户定义的类型添加任何内容
  • 我们也获得了混合操作数(例如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
}

请注意,这些都是“开放”模板,我们采取了必要的预防措施,以确保只有do_access定义为该类型不是一个算术类型。

为何选择这些注意事项?

好。我们将进行强大的移动:我们将把运算符重载注入boost::test_tools命名空间,以便BOOST_CHECK*宏实现可以找到它们。

如果我们没有采取上面提到的预防措施,我们会因为操作员过载模糊而引起很多问题,因为我们并不关心这些类型。


Power Grab

强力抓取很简单:我们在using命名空间内注入(boost::test_tools)每个操作员模板。

现在我们很高兴:

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