65.9K
CodeProject 正在变化。 阅读更多。
Home

测试 C++ 类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (23投票s)

2011 年 7 月 10 日

CPOL

3分钟阅读

viewsIcon

41042

使用指向成员函数的指针测试 C++ 类。

引言

这只是一个关于如何编写测试类来测试 C++ 类的想法。 我可能在这里重新发明了轮子(因为我不是自动化测试的专家...),但是一个想法/代码片段在某些简单情况下可能会证明有用。

背景

熟悉指向 C++ 类函数的指针将有所帮助。 可以在这里找到一个很好的介绍。

支持类

主要的想法是累积指向修改输入值的成员函数的指针,并将它们依次应用于输入实例以获得最终值,并与预期值进行比较。

第一个类 - 称为 functor - 保存指向类 _F 的成员函数的指针,并具有一个名称(该名称仅用于显示目的)

template<typename _F> struct functor {
    _F& (_F:: *_pf)();
    std::string _name;

    functor(_F& (_F::*pf)(), const std::string& name) 
        : _pf(pf)
        , _name(name) {
        }
    functor(const functor& src) 
        : _pf(src._pf)
        , _name(src._name) {
        }
    virtual ~functor() {
        }
    _F& operator()(_F& arg) { 
        return (arg.*_pf)(); }
    const std::string& name() const {
        return _name; }
};

这里感兴趣的是 operator(),它将在对象 arg 上调用成员函数 _pf 并返回对象本身。 在同一对象上多次调用成员函数有点类似于组合函数,其中

fn(...f2(f1(x)) ...)

在我们这里将是

x.f1().f2(). ...

下一个类 workflow 将封装一个类 _F 上的一系列这样的 functor,并将按如上所述的单个步骤调用它们

template<typename _F> 
struct workflow {
    std::vector<functor<_F>> _funcs;

    workflow() {
        }
    virtual ~workflow() {
        }
    workflow& operator+=(const functor<_F>& f) {
        _funcs.push_back(f);
        return *this; }
    _F& operator()(_F& arg) {
        std::vector<functor<_F>>::const_iterator itf;
        std::cout << "==> arg=" << arg << std::endl;
        for(itf = _funcs.begin(); itf != _funcs.end(); itf++) {
            std::cout << " --> " << itf->name().c_str() << " arg=" << arg << std::endl;
            (const_cast<functor<_F>&>(*itf))(arg);
            std::cout << " <-- " << itf->name().c_str() << " arg=" << arg << std::endl;
        }
        std::cout << "<== arg=" << arg << std::endl;
        return arg; }
};

同样,有趣的函数是 operator(),它将在输入对象 arg 上调用 functor 序列,并将返回参数本身。

最后一个类是 tester_t 模板类,它在其构造函数中接受两个参数,即输入值和预期值,具有一个可变参数方法 test,该方法封装了整个测试,并提供强/弱断言支持。

template<typename _C>
struct tester_t {
    _C& _i;
    const _C& _e;

    tester_t(_C& i, const _C& e) 
        : _i(i)
        , _e(e) {
        }
    virtual ~tester_t() {
        }

    bool __cdecl 
    test(
        const char* name, 
        ...
    ) {
        typedef _C& (_C::* _PFMC)();
        typedef std::pair<_PFMC, std::string> PNFMC;

        workflow<_C> wf;

        va_list ap;
        va_start(ap, name);
        do {
            PNFMC* pfnmf = static_cast<PNFMC*>(va_arg(ap, PNFMC*));
            if(!pfnmf) {
                break; }
            else {
                wf += functor<_C>(pfnmf->first, pfnmf->second);
            }
        } while(true);
        va_end(ap);

        std::cout << "Running test:" << name << std::endl;
        wf(_i);

        return _i == _e;
    }

    void assert_succeeded() {
        assert(_i == _e); }
    void assert_fail() {
        assert(_i != _e); }
    void assert_ex(const std::string& msg, bool strong) {
        if(_i != _e) {
            std::cout << "assertion failure: expected " << _e 
                      << " but is " << _i 
                      << " FAILURE" << std::endl; if(strong) {
                assert(_i == _e); }
        }
        else {
            std::cout << "assertion passed : expected " 
              << _e << " and is " << _i 
              << " SUCCESS" << std::endl; }
    }
};
  1. 构造函数的第一个参数是非 const 的,因为应用于对象的 functor 会修改它。 functor 的形式为 "A& A::function()",因为对对象调用 functor 会修改对象本身。(也可以将它们的形式设为 "A A::function()" 甚至非成员函数 "A func(A)" - 任何让你觉得舒服的形式都可以。)第二个参数是 const,因为“预期”值仅用于比较。
  2. test 方法有一些陷阱。 因为它使用可变参数,所以传递给 test 调用的所有参数都是指针(我不知道是否可以将 const 引用传递给 va_arg)。 此外,test 调用的最后一个参数是 0,用于表示函数调用列表的结束。 肯定有更漂亮的方法来向 va_ 调用发出结束信号,但目前我使用了 NULL 指针来停止参数提取。 函数调用参数是 std::pair<指向成员函数的指针, std::string>。 该字符串包含函数名称,并且同样仅用于显示目的。

这些对(函数,名称)累积在 wf workflow 变量中,调用 wf(i); 将调用 workflow operator() 并调用所有累积的 functor。

要测试的类

在此示例中,测试类命名为 integer,它仅封装一个 int 变量并执行一些非常基本的操作(这个没有灵感的名称dmult 来自 double multiply,因为显然不能使用 double)。

struct integer { 
    int _n;

    explicit integer(int n = 0) 
        : _n(n) {
        }
    virtual ~integer() {
        }

    bool operator==(int n) const { 
        return _n == n; }
    bool operator==(const integer& right) const {
        return _n == right._n; }
    bool operator!=(int n) const { 
        return _n != n; }
    bool operator!=(const integer& right) const { 
        return _n != right._n; }

    integer& nop() { 
        return *this; }
    integer& inc() { 
        ++_n; 
        return *this; }
    integer& dec() { 
        --_n; 
        return *this; }
    integer& dmult() { 
        _n *= 2; 
        return *this; }

    friend std::ostream& operator<<(std::ostream& o, const integer& i) {
        o << i._n; 
        return o; }
};

这里没有什么特别的。 同样,添加 operator<< 是为了显示目的。

使用代码

最后,一个简单的测试函数将实例化 inputexpected 对象,通过传递之前的变量来创建 tester 测试对象,通过传递可变参数序列(函数,名称)来执行可变参数 test 调用,以执行 workflow 调用,最后执行基本断言(如注释 assert_succeeded)或者,如本例所示,一个更宽松的 assert_ex 调用,它会打印一条消息。

void 
test_1() {
    integer input(2);
    integer expected(5);
    
    tester_t<integer> tester(input, expected);
    tester.test(
          "integer test (((2+1)*2)-1) ==> 5"
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::inc  , "inc  ")
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::dmult, "dmult")
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::dec  , "dec  ")
        , 0
    );
    //tester.assert_succeeded();
    tester.assert_ex("input 2 expected 5", false);
    std::cout << std::endl;

    return;
}

该图像显示了执行两个顺序测试产生的示例结果图像

历史

  • 1.0 版本 - 2011 年 7 月 10 日。
© . All rights reserved.