单元测试框架
在开发实践中,我目前常用的单元测试框架主要是两个:GTest与TUT。关于GTest我会在其他文章中详细介绍,本文将主要介绍TUT框架的特点及简单的使用方法。
TUT(Template Unit Tests)
TUT的官方主页:http://tut-framework.sourceforge.net/index.html
TUT的SVN地址:http://tut-framework.svn.sourceforge.net/svnroot/tut-framework/trunk
TUT的用户手册:http://tut-framework.sourceforge.net/doxygen/index.html
TUT是一个使用C++编写的单元测试框架,正如名称所示,它是一套C++模板库。下载代码后我们可以发现:它的全部核心是由一系列的.hpp文件组成,不包含任何源文件(example除外),因此,我们在进行单元测试时只需要包含tut的文件即可,无需对TUT进行编译或者链接,像使用STL一样轻松自由,这也是我喜欢它的原因。
TUT使用方法
既然是一个轻量级测试框架,我们应该在半个小时内上手,行了,废话少说,假定我们开发了下面的一个类库:
COperator.h
#include <stdio.h>
class COperator
{
private:
int m_value;
public:
COperator(int value)
{
m_value = value;
}
int add(int value)
{
return m_value += value;
}
int sub(int value)
{
return m_value -= value;
}
};
我们希望对COperator的每个接口进行单元测试,则可以针对COperator类定义一个TestGroup,然后在这个TestGroup编写多个TestCase,每个TestCase对一个或几个接口进行测试,测试代码如下:
test_COperator.cpp
#include <stdio.h>
#include <tut/tut.hpp>
namespace tut
{
// 每个测试组都必须基于一个测试数据的类型才能创建,可以在这个数据结构中放一些可能被该组中多个测试用例使用的数据或者方法,
// 如果没有,也可以什么都不填。最好不要通过此结构的成员在不同测验用例间传递数据。
struct COperatorTestData
{
int GetTestNum()
{
static const int TestNums[] = { 1, 3, 5, 7, 9 };
static int i = 0;
static const int num = sizeof(TestNums) / sizeof(int);
return TestNums[ i++ % num];
}
};
// 创建一个test_group对象时,在构造函数中用一个字符串指明该test group的名称;
// 当存在多个test group时,每个group的名称应该不同,这样可以在启动时单独指定某个test group进行测试;
// 缺省情况下,一个test_group最多50个test case,如果希望更多case,可以在定义时指明上限,如:
// test_group testgroup("COperator",1000);
static test_group testgroup("COperator");
template<>
template<>
void test_group::object::test<1>() // 编写第一个测试用例
{
set_test_name("Test COperatorTestData::add");
int src = GetTestNum();
COperator op(src) ;
ensure("Test COperatorTestData::Add Failed", op.add(1) == src + 1);
}
template<>
template<>
void test_group::object::test<2>() // 编写第二个测试用例
{
set_test_name("Test COperatorTestData::sub");
int src = GetTestNum();
COperator op(src) ;
ensure("Test COperatorTestData::Sub Failed", op.sub(2) == src - 2);
}
template<>
template<>
void test_group::object::test<3>() // 没事找茬,写个错误case看看
{
set_test_name("Test Tut::ensure");
int src = GetTestNum();
COperator op(src) ;
ensure("Test COperatorTestData::Sub Failed", op.sub(2) == src - 1);
}
}
如果还需要测试其他类,我也可以在编写如test_*.cpp等一系列单元测试用例,最终通过main函数将用例综合起来: test_main.cpp
#include <tut/tut.hpp>
#include <tut/tut_console_reporter.hpp>
#include <tut/tut_main.hpp>
#include <iostream>
static tut::test_runner_singleton runner;
int main(int argc, const char* argv[])
{
tut::console_reporter reporter;
tut::runner.get().set_callback(&reporter);
try
{
if(tut::tut_main(argc, argv))
{
if(reporter.all_ok())
{
return 0;
}
else
{
std::cerr << "/nFAILURE and EXCEPTION in these tests are FAKE ;)" << std::endl;
}
}
}
catch(const tut::no_such_group &ex)
{
std::cerr << "No such group: " << ex.what() << std::endl;
}
catch(const tut::no_such_test &ex)
{
std::cerr << "No such test: " << ex.what() << std::endl;
}
catch(const tut::tut_error &ex)
{
std::cout << "General error: " << ex.what() << std::endl;
}
return 0;
}
我们只需要将test_*.cpp连起来编译即可,假如生成一个test.exe文件,我们可以用
test.exe --help 查看帮助
test.exe 运行所有TestGroup
test.exe "COperator" 运行名称为"COperator"的TestGroup
test.exe "COperator" 1 运行名称为"COperator"的TestGroup的第一个测试用例。
TUT的输出
TUT目前支持三种报告的输出方式:console_reporter, xml_reporter,cppunit_reporter。另外,也可以自己继承tut::callback,编写自己的输出方式。下面,以xml方式输出为例,可以如下修改test_main.cpp:
...
//#include <tut/tut_console_reporter.hpp>
#include <tut/tut_xml_reporter.hpp>
...
int main(int argc, const char* argv[])
{
tut::xml_reporter reporter(std::cout); // 将xml输出到stdio,也可以选择输出到某个字符串中
tut::runner.get().set_callback(&reporter);
...
}
执行所有用例,输出结果如下:
<xml version="1.0" encoding="utf-8" standalone="yes"?>
<testsuites>
<testsuite errors="0" failures="1" tests="3" name="COperator">
<testcase classname="COperator" name="Test COperatorTestData::add"/>
<testcase classname="COperator" name="Test COperatorTestData::sub"/>
<testcase classname="COperator" name="Test Tut::ensure">
<failure message="Test COperatorTestData::Sub Failed" type="Assertion">Test COperatorTestData::Sub Failed</failure>
<testcase>
<testsuite>
<testsuites>
TUT的断言
TUT支持一系列ensure开头的断言函数,一旦断言失败则该case不再继续执行,转而执行下一个case,下面介绍几种常用的断言,其余的建议自己参考tut_assert.hpp与tut_posix.hpp
// 断言某个条件为真
void ensure(bool cond);
// 断言某个条件为假
void ensure_not(bool cond);
// 断言某个条件为真,否则输出msg
template <typename M>
void ensure(const M& msg, bool cond);
// 与ensure类似,不过在posix系统中会在msg后输出errno
template<typename M>
void ensure_errno(const M& msg, bool cond);
// 断言某个条件假,否则输出msg
template <typename M>
void ensure_not(const M& msg, bool cond);
// 断言两个值相等
template <typename LHS, typename RHS>
void ensure_equals(const LHS& actual, const RHS& expected);
// 断言两个值相等,否则输出msg
template <typename M, typename LHS, typename RHS>
void ensure_equals(const M& msg, const LHS& actual, const RHS& expected);
// 断言两个浮点数相差不超过某个限定值,否则输出msg
template<typename M>
void ensure_equals(const M& msg, const double& actual, const double& expected, const double& epsilon);
// 断言两个集合相等,否则输出msg
template<typename LhsIterator, typename RhsIterator>
void ensure_equals(const std::string &msg,
const LhsIterator &lhs_begin, const LhsIterator &lhs_end,
const RhsIterator &rhs_begin, const RhsIterator &rhs_end);
// 断言两个值距离小于distance,否则输出msg
template <typename M, class T>
void ensure_distance(const M& msg, const T& actual, const T& expected, const T& distance);
// 相当于直接断言错误,并输出msg
void fail(const char* msg = "");
// 跳过某个测试用例剩余的代码(不视为断言错误),并打印msg
void skip(const char* msg = "");
TUT的断言
作者:icefireelf