测试 C++ 类
使用指向成员函数的指针测试 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; }
}
};
- 构造函数的第一个参数是非 const 的,因为应用于对象的 functor 会修改它。 functor 的形式为 "
A& A::function()
",因为对对象调用 functor 会修改对象本身。(也可以将它们的形式设为 "A A::function()
" 甚至非成员函数 "A func(A)
" - 任何让你觉得舒服的形式都可以。)第二个参数是const
,因为“预期”值仅用于比较。 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<<
是为了显示目的。
使用代码
最后,一个简单的测试函数将实例化 input
和 expected
对象,通过传递之前的变量来创建 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 日。