C/C++只提供了很有限的正确性验证/测试支持——assert宏(没错,它是一个宏,虽然它违背常识使用了小写的形式),这是很不够的。C++标准中的 std: : exception能够处理运行时异常、但并不能检查代码的逻辑,缺乏足够的、语言级别的工具来保证软件的正确性,使程序员很容易陷入与bug搏斗的泥沼中。
Boost 在这方面前进了一大步: boost.assert库增强了原始的运行时assert 宏,static_assert库提供了静态断言(编译期诊断),而 lightweight_test(轻量)和 test库则构建了完整的单元测试框架。
assert
为使用boost.assert需要包含头文件<boost/assert.hpp>,
即:#include <boost/assert.hpp>
基本用法
# define BOOST_ASSERT(expr) assert (expr)
#define BOOST_ASSERT_MSG(expr, msg) assert(expr)&&(msg))
宏的参数expr表达式可以是任意(合法的)c++表达式,从简单的关系比较到复杂的函数嵌套调用都允许。如果表达式值为true,那么断言成立,程序会继续向下执行,否则断言会引发一个异常,在终端上输出调试信息并终止程序的执行。
BOOST_ASSERT宏仅会在 debug模式下生效,在release模式下不会进行编译,不会影响到运行效率,所以可以放心大胆地在代码中使用BoosT_ASSERT 断言。它不仅能够诊断错误、保证程序正确运行,而且其规范的大写形式也能够起到类似代码注释的作用,提醒代码维护人员什么该做、什么不该做。
double func(int x)
{
BOOST_ASSERT_MSG(x!=0,"divided by zero");
return 1.0/x;
}
禁用断言
如果在头文件<boost/assert.hpp>之前定义了宏 BOOST_DISABLE_ASSERTS,那么BOOST_ASSERT将会定义为((void)0),自动失效。但标准的assert宏并不会受影响,这可以让程序员有选择地关闭BOOST_ASSERT。
#include<cassert>
#include<iostream>
#include<boost/assert.hpp>
#define BOOST_DISABLE_ASSERTS
double func(int x)
{
BOOST_ASSERT(x!=0&&"divided by zero");
std::cout<<"after boost_assert"<<std::endl;
assert(x!=0&&"divided by zero");
std::cout<<"after assert"<<std::endl;
return 1.0/x;
}
int main()
{
func(0);
}
//
Assertion `x!=0&&"divided by zero"' failed.
扩展用法
如果在头文件<boost/assert.hpp>之前定义了宏BOOST_ENABLE_ASSERT_HANDLER,这将导致BOOST_ASSERT的行为发生改变:
它将不再等同于assert宏,断言的表达式无论是在 debug还是release模式下都将被求值。如果断言失败,会发生一个断言失败的函数调用boost::assertion_failed ()或者assertion_failed_msg()——这相当于提供了一个错误处理handlere
namespace boost{
void assertion_failed (char const * expr,char const *
function,char const * file,long line) ;
void assertion_failed_msg(char const * expr,
char const * msg,char const * function,char const * file,long line) ;
static_assert
static_assert库把断言的诊断时刻由运行期提前到编译期,让编译器检查可能发生的错误,能够更好地增加程序的健壮性,它模拟实现了C++标准里的static assert关键字。
用法
BOOST_STATIC_ASSERT使用了较复杂的技术,但简单来理解,它实际上最终是一个typedef,因此在编译时同样不会产生任何代码和数据,对运行效率不会有任何影响——不论是 debug模式还是release模式。
BOOST_STATIC_ASSERT是一个编译期断言,使用了模板元编程技术实现,虽然在很多方面它都与 BO0ST_ASSERT很相似,但用法还是有所不同的。最重要的区别是使用范围:BOOST_ASSERT (expr)必须是一个能够执行的语句,它只能在函数域里出现,而 BOOSTSTATIC_ASSERT则可以出现在程序的任何位置:名字空间域、类域或函数域。
template<typename T>
T my_init(T a,T b)
{
BOOST_STATIC_ASSERT(sizeof(T)<sizeof(int))
return a<b? a:b;
}
int main()
{
std::cout<<my_init((short)1,(short)3);
std::cout<<my_init(1L,3L);
}
//
如果使用支持C++11的编译器(如ccc4.6以上、clang3.o 以上),那么我们可以使用BOOST_STATIC_ASSERT_MSG来输出更明确的错误信息提示:
BOOST_STATIc_ASSERT_MSG(sizeof(T)< sizeof(int), "only short or char");这样提示信息会是:
error: static assertion failed: only short or char
使用建议:
static assert主要在泛型编程或模板元编程中用于验证编译期常数或者模板类型参数,可能读者暂时不会用到它,但随着对C++认识的深入,将来就会发现它的好处。
使用static_assert时还需要注意,断言的表达式必须能够在编译期求值,可能会让很多人不太适应,这很正常,因为这正是迈向C++泛型编程和模板元编程的第一步。
test
test库提供了一个用于单元测试的基于命令行界面的测试套件“Unit TestFramework”,简称UTF,还附带有检测内存泄漏的功能,比其他的单元测试库更强大更方便好用。它不仅能够支持简单的测试,也能够支持全面的单元测试,并且还具有程序运行监控功能,是一个用于保证程序正确性的强大工具。
test库包含两个库文件,需要编译才能使用,在jamfile里指定lib的语句如下:
lib boost_unit_test_framework ;//单元测试框架库
lib boost test exec monitor;\\//程序执行监视库
#include<boost/test/unit_test.hpp>
最小化测试
#include<boost/test/minimal.hpp>
入口函数:
minimal test内部已经实现了main ( ),因此我们不必再编写自己的main(),只需再要实现一个test_main ()函数,它是minimal test的真正功能函数。
test_main ()函数的声明与标准的main ()很相似:
int test main ( int argc, char* argv [])
测试断言
提供四个测试断言宏
BOOST_CHECK(e) | 断言测试通过 不通过不影响程序执行 |
BOOST_REQUIRE(e) | 要求测试必须通过 否则程序停止执行 |
BOOST_ERROR(S) | 给出一个错误信息 程序继续执行 |
BOOST_FAIL(s) | 给出一个错误 程序运行终止进行 |
实例测试format
#include<boost/format.hpp>
#include<boost/test/minimal.hpp>
#include<iostream>
int test_main(int argc,char* argv[])
{
using namespace boost;
format fmt("%d-%d");
BOOST_CHECK(fmt.size()!=0);
fmt %12 %34;
BOOST_REQUIRE(fmt.str()=="12-34");
BOOST_ERROR("演示一条错误信息");
fmt.clear();
fmt%12;
try
{
std::cout<<fmt;
}
catch(...)
{
BOOST_FAIL("致命错误,测试终止");
}
return 0;
}
//
演示一条错误信息 in function: 'int test_main(int, char**)'
致命错误,测试终止 in function: 'int test_main(int, char**)'
单元测试框架
test库提供了强大的单元测试框架(UTF),它为软件开发的基本领域——单元测试提供了简单而富有弹性的解决方案,可以满足开发人员从高到低的各种需求,它的优点包括:
易于理解,任何人都可以很容易地构建单元测试模块;
提供测试用例、测试套件的概念,并能够以任意的复杂度组织它们;提供丰富的测试断言,能够处理各种情况,包括C++异常;
可以很容易地初始化测试用例、测试套件或者整个测试程序;可以显示测试进度,对于大型测试非常有用;
测试信息可以显示为多种格式,如普通文本或者XML格式;支持命令行,可以指定运行任意一个测试套件或测试用例;
支持命令行,可以指定运行任意一个测试套件或测试用例;
还有许多更高级的用法。
测试断言
test 库同样提供minimal test 里的四个基本的测试断言: BOOST_CHECK、BOOSTREQUIRE、BOOST_ERROR和 BOOST_FAIL(但实现是不同的)。它们是最基本的测试断言,能够在任何地方使用,但同时为了通用性也不具有其他断言的好处,我们应当尽量少使用。
测试断言的形式是 BOOST_LVL_XXX,例如BOOST_CHECK_EQUAL、BOOST_WARN_GT,详细命名规则如下:
BOOST BOOST
遵循Boost库的命名规则,宏一律以大写的 BOOST开头;
LVL
断言的级别。WARN是警告级,不影响程序运行,也不增加错误数量;CHECK是检查级别,如果断言失败增加错误数量,但不影响程序运行:REQUIRE是最高的级别,如果断言失败将增加错误数量并终止程序运行。最常用的断言级别是CHECK,WARN可以用于不涉及程序关键功能的测试,只有当断言失败会导致无法继续进行测试时才能够使用REQUIRE;
XXX
各种具体的测试断言,如断言相等/不等、抛出/不抛出异常、大于或小于等。
常用的断言:
BOOST_LVL_EQUAL(1,r) | 检测l==r,当测试失败时会给出详细的信息。它不能用于浮点数的比较,浮点数的相等比较应使用 BOOST_LVL_CLOSE; |
BOOST_LVL_GE(1,r) | :检测l>=r,同样的还有GT (l>r)、LT ( l<r)、LE(l<=r)和 NE( l!=r),它们用于测试各种不等性; |
BOOST_LVL_THROW(e,ex) | 检测表达式e抛出指定的ex异常; |
BOOST_LVL_NO_THROW(e) | 检测表达式e不抛出任何异常; |
BOOST_LVL_MESSAGE(e,msg) | 它与不带 MESSAGE后缀的断言功能相同,但测试失败时给出指定的消息; |
BOOST_TEST_NESSAGE(msg) | 它仅输出通知用的信息,不含有任何警告或者错误,默认情况不会显示。 |
测试主体
test库将测试程序定义为一个测试模块,由测试安装、测试主体、测试清理和测试运行器四个部分组成。测试主体是测试模块的实际运行部分,由测试用例和测试套件组织成测试树的形式。
测试用例:
测试用例是一个包含多个测试断言的函数,它是可以被独立执行测试的最小单元,各个测试用例之间是无关的,发生的错误不会影响到其他测试用例。
要添加测试用例需要向UTF注册。在 test库中,可以采用手工或者自动两种形式,通常自动的方式更加简单易用,可以简化测试代码的编写。我们使用宏 BOOST_AoTO_TEST_CASE像声明函数一样创建测试用例,它的定义是:
#define BOOST_AUTO_TEST_CASE(test_name)
BOOST_AUTO_TEST_CASE(t_casel)
{
BOOST_CHECK_EQUAL(1,1)'
....
}
测试套件
测试套件是测试用例的容器,它包含一个或多个测试用例,可以将繁多的测试用例分组管理,共享安装/清理代码,更好地组织测试用例。
测试套件同样可以有手工和自动两种使用方式,
自动方式使用两个宏 BOOST_AUTO_TEST_SUITE和BOOST_AUTO_TEST_SUITE_END,它们的定义是:
#define BOOST_AUTO_TEST_SUITE(suite_name)
#define BOOST_AUTO_TEST_SUITE_END;
这两个宏必须成对使用,宏之间的所有测试用例都属于这个测试套件。一个c++源文件中可以有任意多个测试套件,测试套件也可以任意嵌套,没有深度的限制。测试套件的名字一律以s开头,例如:
BOOST_AUto_TEST_SUITE((s_suite)
BOOST_AUTO_TEST_CASE(t_case1)
{
BOOST_CHECK_EQUAL(1,1);
....
}
BOOST_AUTO_CASE(t_CASE2)
{
BOOST_CHECK_EQUAL(1,1);
...
}
BOOST_AUto_TEST_SUITE_END();
主测试套件
任何UTF单元测试程序都必须存在唯一一个主测试套件,它是整个测试树的根节点,其他的测试套件都是它的子节点。
主测试套件的定义可以使用宏BOOST_TEST_MAIN或者BOOST_TEST_MODULE,它们必须出现在<boost/test/unit_test.hpp>之前。
定义了主测试套件的源文件里可以不使用宏 BOOST_AUTO_TEST_SUITE 和 BOOST.AUTO_TEST_SUITE_END来组织测试用例,因为此时所有测试用例都自动属于主测试套件(当然用了也不算错)。
测试实例
#define BOOST_TEST_MAIN//定义主测试套件
#include<boost/test/included/unit_test.hpp>
#include<boost/smart_ptr.hpp>
using namespace boost;
BOOST_AUTO_TEST_SUITE(s_smart_prt);
BOOST_AUTO_TEST_CASE(t_soped_ptr)
{
scoped_ptr<int>p(new int(857));
BOOST_CHECK(p);
BOOST_CHECK_EQUAL(*p,874);
p.reset();
BOOST_CHECK(p==0);
}
BOOST_AUTO_TEST_CASE(t_share_ptr)
{
shared_ptr<int>p1(new int(100));
BOOST_CHECK(p1);
BOOST_CHECK_EQUAL(*p1,100);
BOOST_CHECK_EQUAL(p1.use_count(),1);
shared_ptr<int>p2=p1;
BOOST_CHECK(p2);
BOOST_CHECK_EQUAL(p2.use_count(),2);
*p1=255;
BOOST_CHECK_EQUAL(*p1,225);
BOOST_CHECK_GT(*p1,200);
}
BOOST_AUTO_TEST_SUITE_END()
//
Running 2 test cases...
/root/boost/02/smart_ptr.cpp(17): error: in "s_smart_prt/t_soped_ptr": check *p == 874 has failed [857 != 874]
/root/boost/02/smart_ptr.cpp(34): error: in "s_smart_prt/t_share_ptr": check *p1 == 225 has failed [255 != 225]
*** 2 failures are detected in the test module "Master Test Suite"
测试夹具
测试安装执行测试前的准备工作,初始化测试用例或测试套件所需的数据。测试清理是测试安装的反向操作,执行必要的清理工作。
测试安装和测试清理的动作很像C++中的构造函数和析构函数,故可以定义一个辅助类,它的构造函数和析构函数分别执行测试安装和测试清理,之后我们就可以在每个测试用例最开始声明一个对象,它将自动完成测试安装和测试清理。
基于这个基本原理,UTF中定义了“测试夹具”的概念,它实现了自动的测试安装和测试清理,就像是一个夹在测试用例或测试套件两端的“夹子”。测试夹具不仅可以用于测试用例,也可以用于测试套件和单元测试全局。
使用夹具 必须是一个类
struct test_fixture_name
{
test_fixture_name(){}
~test_fixture_name(){}
}
指定测试用例和测试套件的夹具类需要使用另外两个宏:
#define BOOST_FIXTURE_TEST_SUITE( suite_name,fixture_name )
#define BOOST_FIXTURE_TEST_CASE ( test_name,fixture_name)
全局测试夹具需要使用另一个宏 BOOST_GLOBAL_FIXTURE,它定义的夹具类被应用于整个测试模块的所有测试套件——包括主测试套件。
#define BOOST_TEST_MAIN
#include<boost/test/included/unit_test.hpp>
#include<boost/assign.hpp>
#include<vector>
#include<iostream>
using namespace boost;
// 全局
struct global_fixture{
global_fixture(){std::cout<<("globakl setup\n");}
~global_fixture(){std::cout<<("global teardown\n");}
};
// 定义全局夹具类
BOOST_GLOBAL_FIXTURE(global_fixture);
// 测试套件
struct assign_fixture
{
assign_fixture()
{std::cout<<("suit setip\n");}
~assign_fixture()
{
std::cout<<("suit: teardown");
}
std::vector<int>v;
};
BOOST_FIXTURE_TEST_SUITE(s_assign,assign_fixture);
BOOST_AUTO_TEST_CASE(t_assign)
{
using namespace boost::assign;
v+=1,2,3,4;
BOOST_CHECK_EQUAL(v.size(),4);
BOOST_CHECK_EQUAL(v[2],3);
}
BOOST_AUTO_TEST_CASE(t_assign2)
{
using namespace boost::assign;
push_back(v)(10)(20)(30);
BOOST_CHECK_EQUAL(v.size(),3);
BOOST_CHECK_GE(v[0],v[1]);
}
//
globakl setup
Running 2 test cases...
suit setip
suit: teardownsuit setip
/root/boost/02/assgin_test.cpp(43): error: in "s_assign/t_assign2": check v[0] >= v[1] has failed [10 < 20]
suit: teardown
*** 1 failure is detected in the test module "Master Test Suite"
global teardown
BOOST_AUTO_TEST_SUITE_END()