When you use a decorator, you're replacing one function with another. In other words, if you have a decorator
- def logged(func):
- def with_logging(*args, **kwargs):
- print func.__name__ + " was called"
- return func(*args, **kwargs)
- return with_logging
then when you say
- @logged
- def f(x):
- """does some math"""
- return x + x * x
it's exactly the same as saying
- def f(x):
- """does some math"""
- return x + x * x
- f = logged(f)
and your function f is replaced with the function with_logging. Unfortunately, this means that if you then say
it will print with_logging
because that's the name of your new function. In fact, if you look at the docstring for f, it will be blank because with_logging has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won't be listed as taking one argument x
; instead it'll be listed as taking *args
and **kwargs
because that's what with_logging takes.
If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have functools.wraps
. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And since wraps
is itself a decorator, the following code does the correct thing:
- from functools import wraps
- def logged(func):
- @wraps(func)
- def with_logging(*args, **kwargs):
- print func.__name__ + " was called"
- return func(*args, **kwargs)
- return with_logging
-
- @logged
- def f(x):
- """does some math"""
- return x + x * x
-
- print f.__name__ # prints 'f'
- print f.__doc__ # prints 'does some math'
转自http://blog.csdn.net/wanghai__/article/details/7078792
Linux 支持的共享程序库(lib*.so)技术不仅能够有效利用系统资源,而且还对程序设计带来了很大的便利性、通用性等,因此被各种级别的应用系统广泛采用。 动态链接的共享库是在加载应用程序时被加载的,而且它与应用程序是在运行时绑定的:通过动态链接器,将动态共享库映射进应用程序的可执行内存中(动态链接);在启动应用程序时,动态装载器将所需的共享目标库映射到应用程序的内存(动态装载)。
在通常情况下,共享库都是通过使用附加选项 -fpic 或 -fPIC 进行编译,从目标代码产生位置无关的代码(Position Independent Code,PIC),使用 -shared选项将目标代码放进共享目标库中。位置无关代码需要能够被加载到不同进程的不同地址,并且能得以正确的执行,故其代码要经过特别的编译处理:位置无关代码(PIC)对常量和函数入口地址的操作都是采用基于基寄存器(base register)BASE+ 偏移量的相对地址的寻址方式。即使程序被装载到内存中的不同地址,即 BASE 值不同,而偏移量是不变的,所以程序仍然可以找到正确的入口地址或者常量。
然而,当应用程序链接了多个共享库,如果在这些共享库中,存在相同作用域范围的同名静态成员变量或者同名 ( 非静态 ) 全局变量,那么当程序访问完静态成员变量或全局变量结束析构时,由于某内存块的 double free 会导致 core dump,这是由于 Linux 编译器的缺陷造成的。
应用场景原型
该问题源于笔者所从事的开发项目:IBM Tivoli Workload Scheduler (TWS) LoadLeveler。LoadLeveler是 IBM在高性能计算(High Performance Computing,HPC)领域的一款作业调度软件。它主要分为两个大的模块,分别是调度模块(scheduler)和资源管理模块(resource manger)。 两个模块中分别含有关于配置管理功能的共享库,由于某些配置管理选项为两模块所共同采用,所以两模块之间共享了部分源文件代码,其中包含有同名的类静态成员。
可以通过以下简单的模型进行描述:
图 1. 应用场景 对应的各模块代码片段如下图所示:
图 2. 应用场景模拟代码 其中,test.c 是主程序,包含有两个头文件:api1.h 与 api2.h;头文件 api1.h 包含头文件 lib1/lib.h 和一功能函数 func_api1(),api2.h 包含头文件 lib2/lib.h 和一功能函数 func_api2();目录 lib1 和 lib2 下的源文件分别编译生成共享库 lib1.so 和 lib2.so。同时,头文件 lib1/lib.h 与 lib2/lib.h 链接到同一共享文件 lib.h。在文件 lib.h 中定义有一静态成员变量“static std::vector<int> vec_int”。
回页首
功能函数与各静态成员函数代码清单
功能函数 func_api1() 与 func_api2() 的实现类似,通过调用静态成员函数达到访问静态成员变量 vec_int的目的:
清单 1. 功能函数 func_api1(int) void func_api1(int i) { printf("%s.\n", __FILE__); A::set(i); A::print(); return; } |
静态成员函数 A::set() 与 A::print() 的实现如下:
清单 2. 静态成员函数 A::set(int) void A::set(int num) { vec_int.clear(); for (int i = 0; i < num; i++) { vec_int.push_back(i); } return; } |
清单 3. 静态成员函数 A::print() void A::print() { for (int i = 0; i < vec_int.size(); i++) { printf("vec_int[%d] = %d, addr: %p.\n", i, vec_int[i], &vec_int[i]); } printf("vec_int addr: %p.\n", &vec_int); return; } |
A::set() 对静态成员 vec_int进行赋值操作,而 A::print() 则打印其中的值与当前项的内存地址。
回页首
运行结果
如果两个共享库是通过选项 -fpic或 -fPIC编译的话,运行程序 test,输出如下:
清单 4. 选项 -fPIC 的测试结果 $ export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH $ g++ -g -o lib1.so -fPIC-rdynamic -shared lib1/lib.c $ g++ -g -o lib2.so -fPIC-rdynamic -shared lib2/lib.c $ g++ -g -o test -L./ -l1 -l2 test.c $ ./test api1.h. vec_int[0] = 0, addr: 0x9cbf028. vec_int[1] = 1, addr: 0x9cbf02c. vec_int[2] = 2, addr: 0x9cbf030. vec_int[3] = 3, addr: 0x9cbf034. vec_int addr: 0xe89228. *** glibc detected *** ./test: double free or corruption (fasttop): 0x09cbf028*** ======= Backtrace:========= /lib/libc.so.6[0x2b2b16] /lib/libc.so.6(cfree+0x90)[0x2b6030] /usr/lib/libstdc++.so.6(_ZdlPv+0x21)[0x5d1731] ./lib1.so(_ZN9__gnu_cxx13new_allocatorIiE10deallocateEPij+0x1d)[0xe88417] ./lib1.so(_ZNSt12_Vector_baseIiSaIiEE13_M_deallocateEPij+0x33)[0xe88451] ./lib1.so(_ZNSt12_Vector_baseIiSaIiEED2Ev+0x42)[0xe8849a] ./lib1.so(_ZNSt6vectorIiSaIiEED1Ev+0x60)[0xe8850c] ./lib2.so[0x961d6c] /lib/libc.so.6(__cxa_finalize+0xa9)[0x275c79] ./lib2.so[0x961c34] ./lib2.so[0x962d3c] /lib/ld-linux.so.2[0x23a7de] /lib/libc.so.6(exit+0xe9)[0x2759c9] /lib/libc.so.6(__libc_start_main+0xe4)[0x25fdf4] ./test(__gxx_personality_v0+0x45)[0x80484c1] ======= Memory map:======== ...... 00960000-00963000 r-xp 00000000 00:1b 7668734 ./lib2.so 00963000-00964000 rwxp 00003000 00:1b 7668734 ./lib2.so 00970000-00971000 r-xp 00970000 00:00 0 [vdso] 00e86000-00e89000 r-xp 00000000 00:1b 7668022 ./lib1.so 00e89000-00e8a000 rwxp 00003000 00:1b 7668022 ./lib1.so 08048000-08049000 r-xp 00000000 00:1b 7668748 ./test 08049000-0804a000 rw-p 00000000 00:1b 7668748 ./test 09cbf000-09ce0000 rw-p 09cbf000 00:00 0 [heap] ...... Abort(coredump) $ |
从程序的输出直观的看到,core 产生是由于堆内存区域(09cbf000-09ce0000)中起始地址为 0x09cbf028的内存区被释放了两次导致的,该地址正式静态成员变量 vec_int的第一个元素的地址。
为什么会出现同一块内存区,被释放两次的情形呢?
回页首
原因分析
我们知道,静态成员变量与全局变量类似,都采用了静态存储方式。对于加了选项 -fpic或 -fPIC的共享库,这些变量的地址都存放在该共享库的全局偏移表(Global Offset Table,GOT)中。
通过 objdump或者 readelf命令分析共享库 lib1.so,结果如下:
清单 5. objdump 分析共享库 lib1.so 的输出 $ objdump -x -R lib1.so lib1.so: file format elf32-i386 ...... Sections: Idx Name Size VMA LMA File off Algn 0 .gnu.hash 000001e8 000000d4 000000d4 000000d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ...... 18 .dynamic 000000d8 0000301c 0000301c 0000301c 2**2 CONTENTS, ALLOC, LOAD, DATA 19 .got 00000014 000030f4 000030f4 000030f4 2**2 CONTENTS, ALLOC, LOAD, DATA 20 .got.plt 00000114 00003108 00003108 00003108 2**2 CONTENTS, ALLOC, LOAD, DATA ...... DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE ...... 000030f4 R_386_GLOB_DAT __gmon_start__ 000030f8 R_386_GLOB_DAT _Jv_RegisterClasses 000030fc R_386_GLOB_DAT _ZN1A7vec_intE 00003104 R_386_GLOB_DAT __cxa_finalize ...... |
清单 6. readelf 分析共享库 lib1.so 的输出 $ objdump -x -R lib1.so lib1.so: file format elf32-i386 ...... Sections: Idx Name Size VMA LMA File off Algn 0 .gnu.hash 000001e8 000000d4 000000d4 000000d4 2**2 CONTENTS, ALLOC, LOAD, READONLY, DATA ...... 18 .dynamic 000000d8 0000301c 0000301c 0000301c 2**2 CONTENTS, ALLOC, LOAD, DATA 19 .got 00000014 000030f4 000030f4 000030f4 2**2 CONTENTS, ALLOC, LOAD, DATA 20 .got.plt 00000114 00003108 00003108 00003108 2**2 CONTENTS, ALLOC, LOAD, DATA ...... DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE ...... 000030f4 R_386_GLOB_DAT __gmon_start__ 000030f8 R_386_GLOB_DAT _Jv_RegisterClasses 000030fc R_386_GLOB_DAT _ZN1A7vec_intE 00003104 R_386_GLOB_DAT __cxa_finalize ...... |
从上面两个命令的输出结果中可以看出,共享库 lib1.so中 GOT段的起始内存地址为 000030f4,大小为 20 字节 (0x14);静态成员变量 vec_int在共享库 lib1.so中的起始偏移地址为 000030fc。显然,vec_int位于该共享库的 GOT段内。
当应用程序同时链接 lib1.so和 lib2.so时,同名静态成员变量 vec_int分别位于其共享库的 GOT区。当程序运行时,系统从符号表中查找并装载构造一份 vec_int数据,这点从程序运行的输出结果(清单 4)的“Backtrace”部分可以看到:只有 lib1.so中的静态成员变量被装载构造;同时,通过内存映射(Memory map)部分(清单 4),可以观察到 vec_int对象的地址 0xe89228正好处在为共享库 lib1.so分配的可读内存区 00e89000-00e8a000中:
00e89000-00e8a000 rwxp 00003000 00:1b 7668022 ./lib1.so |
然后,当程序结束时,却对该变量进行了两次析构操作,通过 gdb分析 core 文件:
清单 7. core 文件分析结果 $ gdb ./test core.28440 …… Core was generated by `./test'. Program terminated with signal 6, Aborted. #0 0x00970402 in __kernel_vsyscall () (gdb) (gdb) where #0 0x00970402 in __kernel_vsyscall () #1 0x00272d10 in raise () from /lib/libc.so.6 #2 0x00274621 in abort () from /lib/libc.so.6 #3 0x002aae5b in __libc_message () from /lib/libc.so.6 #4 0x002b2b16 in _int_free () from /lib/libc.so.6 #5 0x002b6030 in free () from /lib/libc.so.6 #6 0x005d1731 in operator delete () from /usr/lib/libstdc++.so.6 #7 0x00e88417 in __gnu_cxx::new_allocator<int>::deallocate (this=0xe89228, __p=0x9cbf028) at /usr/lib/gcc/i386-redhat-linux/.../ext/new_allocator.h:94 #8 0x00e88451 in std::_Vector_base<int, ... (this=0xe89228, __p=0x9cbf028, __n=4) at /usr/lib/gcc/.../include/c++/4.1.2/bits/stl_vector.h:133 #9 0x00e8849a in ~_Vector_base (this=0xe89228) at /usr/lib/gcc/.../include/c++/4.1.2/bits/stl_vector.h:119 #10 0x00e8850cin ~vector (this=0xe89228) at /usr/lib/gcc/.../stl_vector.h:272 #11 0x00961d6c in __tcf_0 () at lib2/lib.c:3 #12 0x00275c79 in __cxa_finalize () from /lib/libc.so.6 #13 0x00961c34 in __do_global_dtors_aux () from ./lib2.so #14 0x00962d3c in _fini () from ./lib2.so #15 0x0023a7de in _dl_fini () from /lib/ld-linux.so.2 #16 0x002759c9 in exit () from /lib/libc.so.6 #17 0x0025fdf4 in __libc_start_main () from /lib/libc.so.6 #18 0x080484c1 in _start () (gdb) |
从清单 7 中可以看出,从帧 #14 开始,程序进行 lib2.so中的析构操作,直到 #11,都运行在 lib2.so中,当进入帧 #10 时,进行变量析构时,其地址为 0x00e8850c,该地址中的对象是程序启动时由共享库 lib1.so装载构造出来的(清单 1):
./lib1.so(_ZNSt6vectorIiSaIiEED1Ev+0x60)[0xe8850c] |
当程序结束时,运行库 glibc检测到共享库 lib2.so析构了并非由其构造的对象,导致了 core dump。
这种情况下,如果替换使用选项 -fpie或 -fPIE,操作步骤与运行结果如下所示:
清单 8. 选项 -fPIE 的测试结果 $ export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH $ g++ -g -o lib1.so -fPIE-rdynamic -shared lib1/lib.c $ g++ -g -o lib2.so -fPIE-rdynamic -shared lib2/lib.c $ g++ -g -pie -o test -L./ -l1 -l2 test.c $ ./test api1.h. vec_int[0] = 0, addr: 0x80e3028. vec_int[1] = 1, addr: 0x80e302c. vec_int[2] = 2, addr: 0x80e3030. vec_int[3] = 3, addr: 0x80e3034. vec_int addr: 0x75e224. $ |
程序运行结果符合期望并正常结束。
这是因为,当使用选项 -fpie或 -fPIE时,生成的共享库不会为静态成员变量或全局变量在 GOT中创建对应的条目(通过 objdump或readelf命令可以查看,此处不再赘述),从而避免了由于静态对象“构造一次,析构两次”而对同一内存区域释放两次引起的程序 core dump。
选项 -fpie和 -fPIE与 -fpic及 -fPIC的用法很相似,区别在于前者总是将生成的位置无关代码看作是属于程序本身,并直接链接进该可执行程序,而非存入全局偏移表 GOT中;这样,对于同名的静态或全局对象的访问,其构造与析构操作将保持一一对应。
回页首
结束语
通过使用选项 -fpie或 -fPIE代替 -fpic或者 -fPIC,使得生成的共享库不会为静态成员变量或全局变量在 GOT中创建对应的条目,同时也就避免了针对同名静态对象“构造一次,析构两次”的不当操作。
转自:http://www.ibm.com/developerworks/cn/linux/l-cn-sdlstatic/
简介
测试是软件开发过程中极其重要的一环,详尽周密的测试能够减少软件BUG,提高软件品质。测试包括单元测试、系统测试等。其中单元测试是指针对软件功能单元所作的测试,这里的功能单元可以是一个类的属性或者方法,测试的目的是看这些基本单元是否工作正常。由于单元测试的内容很基础,因此可以看作是测试工作的第一环,该项工作一般由开发人员自行完成。如果条件允许,单元测试代码的开发应与程序代码的开发同步进行。
虽然不同程序的单元测试代码不尽相同,但测试代码的框架却非常相似,于是便出现了一些单元测试类库,CppUnit便是其中之一。
CppUnit是XUnit中的一员,XUnit是一个大家族,还包括JUnit和PythonUnit等。CppUnit简单实用,学习和使用起来都很方便,网上已有一些文章对其作介绍,但本文更着重于讲解其中的基本概念和使用方法,以帮助初次接触CppUnit的人员快速入门。
安装
目前,CppUnit的最新版本是1.10.2,你可以从下面地址获取:
http://sourceforge.net/projects/cppunit解压后,你可以看到CppUnit包含如下目录:
config: 配置文件 contrib: contribution,其他人贡献的外围代码 doc: 文档,需要通过doxygen工具生成,也可以直接从sourceforge站点上下载打包好的文档 examples:示例代码 include: 头文件 lib: 存放编译好的库 src: 源文件,以及编译库的工程等
然后打开src目录下的CppUnitLibraries工程,执行build/batch build,编译成功的话,生成的库文件将被拷贝到lib目录下。
你也可以根据需要选择所需的项目进行编译,其中项目cppunit为静态库,cppunit_dll为动态库,生成的库文件为:
cppunit.lib: 静态库release版 cppunitd.lib: 静态库debug版 cppunit_dll.lib: 动态库release版 cppunitd_dll.lib:动态库debug版
要使用CppUnit,还得设置好头文件和库文件路径,以VC6为例,选择Tools/Options/Directories,在Include files和Library files中分别添加%CppUnitPath%/include和%CppUnitPath%/lib,其中%CppUnitPath%表示CppUnit所在路径。
做好准备工作后,我们就可以编写自己的单元测试代码了。需说明的是,CppUnit所用的动态运行期库均为多线程动态库,因此你的单元测试程序也得使用相应设置,否则会发生冲突。
概念
在使用之前,我们有必要认识一下CppUnit中的主要类,当然你也可以先看后面的例子,遇到问题再回过头来看这一节。
CppUnit核心内容主要包括六个方面,
1. 测试对象(Test,TestFixture,...):用于开发测试用例,以及对测试用例进行组织管理。
2. 测试结果(TestResult):处理测试用例执行结果。TestResult与下面的TestListener采用的是观察者模式(Observer Pattern)。
3. 测试结果监听者(TestListener):TestListener作为TestResult的观察者,担任实际的结果处理角色。
4. 结果输出(Outputter):将结果进行输出,可以制定不同的输出格式。
5. 对象工厂(TestFactory):用于创建测试对象,对测试用例进行自动化管理。
6. 测试执行体(TestRunner):用于运行一个测试。
以上各模块的主要类继承结构如下:
Test TestFixture TestResult TestListener _______|_________ | | | | | TestSuccessListener TestComposite TestLeaf | | | |____________| TestResultCollector TestSuit | TestCase | TestCaller<Fixture> Outputter TestFactory TestRunner ____________________|_________________ | | | | TestFactoryRegistry CompilerOutputter TextOutputter XmlOutputter | TestSuiteFactory<TestCaseType>
接下来再对其中一些关键类作以介绍。
Test:所有测试对象的基类。
CppUnit采用树形结构来组织管理测试对象(类似于目录树),因此这里采用了组合设计模式(Composite Pattern),Test的两个直接子类TestLeaf和TestComposite分别表示“测试树”中的叶节点和非叶节点,其中TestComposite主要起组织管理的作用,就像目录树中的文件夹,而TestLeaf才是最终具有执行能力的测试对象,就像目录树中的文件。
Test最重要的一个公共接口为:
virtual void run(TestResult *result) = 0;
其作用为执行测试对象,将结果提交给result。
在实际应用中,我们一般不会直接使用Test、TestComposite以及TestLeaf,除非我们要重新定制某些机制。
TestFixture:用于维护一组测试用例的上下文环境。
在实际应用中,我们经常会开发一组测试用例来对某个类的接口加以测试,而这些测试用例很可能具有相同的初始化和清理代码。为此,CppUnit引入TestFixture来实现这一机制。
TestFixture具有以下两个接口,分别用于处理测试环境的初始化与清理工作:
virtual void setUp();
virtual void tearDown();
TestCase:测试用例,从名字上就可以看出来,它便是单元测试的执行对象。
TestCase从Test和TestFixture多继承而来,通过把Test::run制定成模板函数(Template Method)而将两个父类的操作融合在一起,run函数的伪定义如下:
// 伪代码
void TestCase::run(TestResult* result)
{
result->startTest(this); // 通知result测试开始
if( result->protect(this, &TestCase::setUp) ) // 调用setUp,初始化环境
result->protect(this, &TestCase::runTest); // 执行runTest,即真正的测试代码
result->protect(this, &TestCase::tearDown); // 调用tearDown,清理环境
result->endTest(this); // 通知result测试结束
}
这里要提到的是函数runTest,它是TestCase定义的一个接口,原型如下:
virtual void runTest();
用户需从TestCase派生出子类并实现runTest以开发自己所需的测试用例。
另外还要提到的就是TestResult的protect方法,其作用是对执行函数(实际上是函数对象)的错误信息(包括断言和异常等)进行捕获,从而实现对测试结果的统计。
TestSuit:测试包,按照树形结构管理测试用例
TestSuit是TestComposite的一个实现,它采用vector来管理子测试对象(Test),从而形成递归的树形结构。
TestCaller:TestCase适配器(Adapter),它将成员函数转换成测试用例
虽然我们可以从TestCase派生自己的测试类,但从TestCase类的定义可以看出,它只能支持一个测试用例,这对于测试代码的组织和维护很不方便,尤其是那些有共同上下文环境的一组测试。为此,CppUnit提供了TestCaller以解决这个问题。
TestCaller是一个模板类,它以实现了TestFixture接口的类为模板参数,将目标类中某个符合runTest原型的测试方法适配成TestCase的子类。
在实际应用中,我们大多采用TestFixture和TestCaller相组合的方式,具体例子参见后文。
TestResult和TestListener:处理测试信息和结果
前面已经提到,TestResult和TestListener采用了观察者模式,TestResult维护一个注册表,用于管理向其登记过的TestListener,当TestResult收到测试对象(Test)的测试信息时,再一一分发给它所管辖的TestListener。这一设计有助于实现对同一测试的多种处理方式。
TestFactory:测试工厂
这是一个辅助类,通过借助一系列宏定义让测试用例的组织管理变得自动化。参见后面的例子。
TestRunner:用于执行测试用例
TestRunner将待执行的测试对象管理起来,然后供用户调用。其接口为:
virtual void addTest( Test *test ); virtual void run( TestResult &controller, const std::string &testPath = "" );
这也是一个辅助类,需注意的是,通过addTest添加到TestRunner中的测试对象必须是通过new动态创建的,用户不能删除这个对象,因为TestRunner将自行管理测试对象的生命期。
使用
先让我们看看一个简单的例子:
#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
// 定义测试用例
class SimpleTest : public CppUnit::TestCase
{
public:
void runTest() // 重载测试方法
{
int i = 1;
CPPUNIT_ASSERT_EQUAL(0, i);
}
};
int main(int argc, char* argv[])
{
CppUnit::TestResult r;
CppUnit::TestResultCollector rc;
r.addListener(&rc); // 准备好结果收集器
SimpleTest t;
t.run(&r); // 运行测试用例
CppUnit::TextOutputter o(&rc, std::cout);
o.write(); // 将结果输出
return 0;
}
编译后运行,输出结果为:!!!FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0
1) test: (F) line: 18 E:/CppUnitExamples/SimpleTest.cpp
equality assertion failed
- Expected: 1
- Actual : 0
上面的例子很简单,需说明的是CPPUNIT_ASSERT_EQUAL宏。CppUnit定义了一组宏用于检测错误,CPPUNIT_ASSERT_EQUAL是其中之一,当断言失败时,CppUnit便会将错误信息报告给TestResult。这些宏定义的说明如下:
CPPUNIT_ASSERT(condition):判断condition的值是否为真,如果为假则生成错误信息。
CPPUNIT_ASSERT_MESSAGE(message, condition):与CPPUNIT_ASSERT类似,但结果为假时报告messsage信息。
CPPUNIT_FAIL(message):直接报告messsage错误信息。
CPPUNIT_ASSERT_EQUAL(expected, actual):判断expected和actual的值是否相等,如果不等输出错误信息。
CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual):与CPPUNIT_ASSERT_EQUAL类似,但断言失败时输出message信息。
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta):判断expected与actual的偏差是否小于delta,用于浮点数比较。
CPPUNIT_ASSERT_THROW(expression, ExceptionType):判断执行表达式expression后是否抛出ExceptionType异常。
CPPUNIT_ASSERT_NO_THROW(expression):断言执行表达式expression后无异常抛出。
接下来再看看TestFixture和TestCaller的组合使用:
#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestRunner.h>
// 定义测试类
class StringTest : public CppUnit::TestFixture
{
public:
void setUp() // 初始化
{
m_str1 = "Hello, world";
m_str2 = "Hi, cppunit";
}
void tearDown() // 清理
{
}
void testSwap() // 测试方法1
{
std::string str1 = m_str1;
std::string str2 = m_str2;
m_str1.swap(m_str2);
CPPUNIT_ASSERT(m_str1 == str2);
CPPUNIT_ASSERT(m_str2 == str1);
}
void testFind() // 测试方法2
{
int pos1 = m_str1.find(',');
int pos2 = m_str2.rfind(',');
CPPUNIT_ASSERT_EQUAL(5, pos1);
CPPUNIT_ASSERT_EQUAL(2, pos2);
}
protected:
std::string m_str1;
std::string m_str2;
};
int main(int argc, char* argv[])
{
CppUnit::TestResult r;
CppUnit::TestResultCollector rc;
r.addListener(&rc); // 准备好结果收集器
CppUnit::TestRunner runner; // 定义执行实体
runner.addTest(new CppUnit::TestCaller<StringTest>("testSwap", &StringTest::testSwap)); // 构建测试用例1
runner.addTest(new CppUnit::TestCaller<StringTest>("testFind", &StringTest::testFind)); // 构建测试用例2
runner.run(r); // 运行测试
CppUnit::TextOutputter o(&rc, std::cout);
o.write(); // 将结果输出
return rc.wasSuccessful() ? 0 : -1;
}
编译后运行结果为:OK (2 tests)
上面的代码从功能上讲没有什么问题,但编写起来太繁琐了,为此,我们可以借助CppUnit定义的一套辅助宏,将测试用例的定义和注册变得自动化。上面的代码改造后如下:
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
// 定义测试类
class StringTest : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(StringTest); // 定义测试包
CPPUNIT_TEST(testSwap); // 添加测试用例1
CPPUNIT_TEST(testFind); // 添加测试用例2
CPPUNIT_TEST_SUITE_END(); // 结束测试包定义
public:
void setUp() // 初始化
{
m_str1 = "Hello, world";
m_str2 = "Hi, cppunit";
}
void tearDown() // 清理
{
}
void testSwap() // 测试方法1
{
std::string str1 = m_str1;
std::string str2 = m_str2;
m_str1.swap(m_str2);
CPPUNIT_ASSERT(m_str1 == str2);
CPPUNIT_ASSERT(m_str2 == str1);
}
void testFind() // 测试方法2
{
int pos1 = m_str1.find(',');
int pos2 = m_str2.rfind(',');
CPPUNIT_ASSERT_EQUAL(5, pos1);
CPPUNIT_ASSERT_EQUAL(2, pos2);
}
protected:
std::string m_str1;
std::string m_str2;
};
CPPUNIT_TEST_SUITE_REGISTRATION(StringTest); // 自动注册测试包
int main(int argc, char* argv[])
{
CppUnit::TestResult r;
CppUnit::TestResultCollector rc;
r.addListener(&rc); // 准备好结果收集器
CppUnit::TestRunner runner; // 定义执行实体
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
runner.run(r); // 运行测试
CppUnit::TextOutputter o(&rc, std::cout);
o.write(); // 将结果输出
return rc.wasSuccessful() ? 0 : -1;
}
CppUnit的简单介绍就到此,相信你已经了解了其中的基本概念,也能够开发单元测试代码了。
转自:http://blog.csdn.net/freefalcon/article/details/753819