关于CppUnit里面宏的介绍
来源:互联网 发布:什么软件可以免越狱 编辑:程序博客网 时间:2024/06/16 10:49
本文是讨论开放源码单元测试工具的 系列文章 的第 2 篇,介绍非常受欢迎的 CppUnit — 最初由 Eric Gamma 和 Kent Beck 开发的 JUnit 测试框架的 C++
版本。C++
版本由 Michael Feathers 创建,它包含许多类,有助于进行白盒测试和创建自己的回归测试套件。本文介绍一些比较有用的 CppUnit 特性,比如 TestCase、TestSuite、TestFixture、TestRunner 和辅助宏。
下载和安装 CppUnit
对于本文,我在一台 Linux® 机器(内核 2.4.21)上用 g++-3.2.3 和 make-3.79.1 下载并安装了 CppUnit。安装过程很简单,是标准的:运行configure
命令,然后运行 make
和 make install
。注意,对于 cygwin 等平台,这个过程可能无法顺利地完成,所以一定要通过 INSTALL-unix 文档了解详细的安装信息。如果安装成功,应该会在安装路径(CPPUNIT_HOME)中看到 CppUnit 的 include 和 lib 文件夹。清单 1 给出文件夹结构。
清单 1. CppUnit 安装目录结构
[arpan@tintin] echo $CPPUNIT_HOME/home/arpan/ibm/cppUnit[arpan@tintin] ls $CPPUNIT_HOMEbin include lib man share
要想编译使用 CppUnit 的测试,必须构建源代码:
g++ <C/C++ file> -I$CPPUNIT_HOME/include –L$CPPUNIT_HOME/lib -lcppunit
注意,如果是使用 CppUnit 的共享库版本,可能需要使用 –ldl
选项编译源代码。安装之后,还可能需要修改 UNIX® 环境变量 LD_LIBRARY_PATH 以反映 libcppunit.so 的位置。
回页首
使用 CppUnit 创建基本测试
学习 CppUnit 的最佳方法是创建一个叶级测试(leaf level test)。CppUnit 附带一整套预先定义的类,可以用它们方便地设计测试。为了保持连续性,先回顾一下本系列第 1 部分 中讨论过的字符串类(见清单 2)。
清单 2. 简单的字符串类
#ifndef _MYSTRING#define _MYSTRINGclass mystring { char* buffer; int length; public: void setbuffer(char* s) { buffer = s; length = strlen(s); } char& operator[ ] (const int index) { return buffer[index]; } int size( ) { return length; } }; #endif
与字符串相关的典型检查包括检查空字符串的长度是否为 0 以及访问范围超出索引是否导致错误消息/异常。清单 3 使用 CppUnit 执行这些测试。
清单 3. 字符串类的单元测试
#include <cppunit/TestCase.h>#include <cppunit/ui/text/TextTestRunner.h>class mystringTest : public CppUnit::TestCase {public: void runTest() { mystring s; CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() != 0); }};int main (){ mystringTest test; CppUnit::TextTestRunner runner; runner.addTest(&test); runner.run(); return 0;}
要学习的第一个 CppUnit 类是 TestCase
。要想为字符串类创建单元测试,需要创建 CppUnit::TestCase
类的子类并覆盖runTest
方法。定义了测试本身之后,实例化 TextTestRunner
类,这是一个控制器类,必须在其中添加测试(vide addTest
方法)。清单 4 给出run
方法的输出。
清单 4. 清单 3 中代码的输出
[arpan@tintin] ./a.out!!!FAILURES!!!Test Results:Run: 1 Failures: 1 Errors: 01) test: (F) line: 26 try.ccassertion failed- Expression: s.size() == 0- String Length Non-Zero
为了确认断言确实起作用了,把 CPPUNIT_ASSERT_MESSAGE
宏中的条件改为相反的条件。清单 5 给出条件改为s.size() ==0
之后代码的输出。
清单 5. 条件改为 s.size( ) == 0 之后清单 3 中代码的输出
[arpan@tintin] ./a.outOK (1 tests)
注意,TestRunner
并非运行单一测试或测试套件的惟一方法。CppUnit 还提供另一个类层次结构 — 即模板化的 TestCaller
类。可以不使用 runTest
方法,而是使用 TestCaller
类执行任何方法。清单 6 提供一个小示例。
清单 6. 使用 TestCaller 运行测试
class ComplexNumberTest ... { public: void ComplexNumberTest::testEquality( ) { … } };CppUnit::TestCaller<ComplexNumberTest> test( "testEquality", &ComplexNumberTest::testEquality );CppUnit::TestResult result;test.run( &result );
在上面的示例中,定义了一个类型为 ComplexNumberText
的类,其中包含 testEquality
方法(测试两个复数是否相等)。用这个类对TestCaller
进行模板化,与使用 TestRunner
时一样,通过调用 run
方法执行测试。但是,这样使用TestCaller
类意义不大:TextTestRunner
类会自动显示输出。而在使用 TestCaller
时,必须使用另一个类处理输出。在本文后面使用TestCaller
类定义定制的测试套件时,您会看到这种代码。
回页首
使用断言
清单 7. CPPUNIT_ASSERT_MESSAGE 的定义
#define CPPUNIT_ASSERT_MESSAGE(message,condition) \ ( CPPUNIT_NS::Asserter::failIf( !(condition), \ CPPUNIT_NS::Message( "assertion failed", \ "Expression: " \ #condition, \ message ), \ CPPUNIT_SOURCELINE() ) )
清单 8 给出这个断言使用的failIf
方法的声明。
清单 8. failIf 方法的声明
struct Asserter{… static void CPPUNIT_API failIf( bool shouldFail, const Message &message, const SourceLine &sourceLine = SourceLine() );…}
如果 failIf
方法中的条件为真,就会抛出一个异常。run
方法在内部处理该过程。另一个有意思、有用的宏是CPPUNIT_ASSERT_DOUBLES_EQUAL
,它使用一个容差值检查两个双精度数是否相等(即 |expected – actual | ≤ delta
)。清单 9 给出宏定义。
清单 9. CPPUNIT_ASSERT_DOUBLES_EQUAL 宏定义
void CPPUNIT_API assertDoubleEquals( double expected, double actual, double delta, SourceLine sourceLine, const std::string &message );#define CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,actual,delta) \ ( CPPUNIT_NS::assertDoubleEquals( (expected), \ (actual), \ (delta), \ CPPUNIT_SOURCELINE(), \ "" ) )
回页首
再次测试字符串类
为了测试 mystring
类的其他方面,可以在 runTest
方法中添加更多检查。但是,这么做很快就会变得难以管理了,除非是最简单的类。这时就需要定义和使用测试套件。清单 10 为字符串类定义一个测试套件。
清单 10. 为字符串类定义测试套件
#include <cppunit/extensions/TestFactoryRegistry.h>#include <cppunit/ui/text/TextTestRunner.h>#include <cppunit/extensions/HelperMacros.h>class mystringTest : public CppUnit::TestCase {public: void checkLength() { mystring s; CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() == 0); } void checkValue() { mystring s; s.setbuffer("hello world!\n"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Corrupt String Data", s[0], 'w'); } CPPUNIT_TEST_SUITE( mystringTest ); CPPUNIT_TEST( checkLength ); CPPUNIT_TEST( checkValue ); CPPUNIT_TEST_SUITE_END();};
这很简单。使用 CPPUNIT_TEST_SUITE
宏定义测试套件。mystringTest
类中的方法形成测试套件中的单元测试。我们稍后研究这些宏及其内容,但是先看看使用这个测试套件的客户机代码(见清单 11)。
清单 11. 使用 mystring 类的测试套件的客户机代码
CPPUNIT_TEST_SUITE_REGISTRATION ( mystringTest );int main (){ CppUnit::Test *test = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); CppUnit::TextTestRunner runner; runner.addTest(test); runner.run(); return 0;}
清单 12 给出运行清单 11 时的输出。
清单 12. 清单 10 和清单 11 中代码的输出
[arpan@tintin] ./a.out!!!FAILURES!!!Test Results:Run: 2 Failures: 2 Errors: 01) test: mystringTest::checkLength (F) line: 26 str.ccassertion failed- Expression: s.size() == 0- String Length Non-Zero2) test: mystringTest::checkValue (F) line: 32 str.ccequality assertion failed- Expected: h- Actual : w- Corrupt String Data
CPPUNIT_ASSERT_EQUAL_MESSAGE
的定义在头文件 TestAssert.h 中,它检查预期参数和实际参数是否匹配。如果不匹配,就显示指定的消息。在 HelperMacros.h 中定义的CPPUNIT_TEST_SUITE
宏可以简化创建测试套件并在其中添加测试的流程。在内部创建一个 CppUnit::TestSuiteBuilderContext
类型的模板化对象(这是 CppUnit 上下文中的测试套件),每个CPPUNIT_TEST
调用在套件中添加相应的类方法。类方法作为代码的单元测试。请注意宏的次序:编译各个 CPPUNIT_TEST
宏的代码必须在CPPUNIT_TEST_SUITE
和 CPPUNIT_TEST_SUITE_END
宏之间。
回页首
组织新测试
随着时间的推移,开发人员会不断添加功能,这些功能也需要测试。在同一测试套件中不断添加测试会逐渐造成混乱,而且对首次测试的修改容易随着修改的不断增加而丢失。好在 CppUnit 提供一个有用的CPPUNIT_TEST_SUB_SUITE
宏,可以使用它扩展现有的测试套件。清单 13 使用这个宏。
清单 13. 扩展测试套件
class mystringTestNew : public mystringTest {public: CPPUNIT_TEST_SUB_SUITE (mystringTestNew, mystringTest); CPPUNIT_TEST( someMoreChecks ); CPPUNIT_TEST_SUITE_END(); void someMoreChecks() { std::cout << "Some more checks...\n"; }};CPPUNIT_TEST_SUITE_REGISTRATION ( mystringTestNew );
注意,新的类 mystringTestNew
是从前面的 myStringTest
类派生的。CPPUNIT_TEST_SUB_SUITE
宏的两个参数是新的类和它的超类。在客户端,只注册这个新类,不需要注册两个类。语法的其他部分与创建测试套件的语法相同。
回页首
使用 fixtures 定制测试
在 CppUnit 上下文中,fixture 或 TestFixture
用于为各个测试提供简洁的设置和退出例程。要想使用 fixture,测试类应该派生自CppUnit::TestFixture
并覆盖预先定义的 setUp
和 tearDown
方法。在执行单元测试之前调用setUp
方法,在测试执行完时调用 tearDown
。清单 14 演示如何使用TestFixture
。
清单 14. 使用测试 fixture 定制测试套件
#include <cppunit/extensions/TestFactoryRegistry.h>#include <cppunit/ui/text/TextTestRunner.h>#include <cppunit/extensions/HelperMacros.h>class mystringTest : public CppUnit::TestFixture {public: void setUp() { std::cout << “Do some initialization here…\n”; } void tearDown() { std::cout << “Cleanup actions post test execution…\n”; } void checkLength() { mystring s; CPPUNIT_ASSERT_MESSAGE("String Length Non-Zero", s.size() == 0); } void checkValue() { mystring s; s.setbuffer("hello world!\n"); CPPUNIT_ASSERT_EQUAL_MESSAGE("Corrupt String Data", s[0], 'w'); } CPPUNIT_TEST_SUITE( mystringTest ); CPPUNIT_TEST( checkLength ); CPPUNIT_TEST( checkValue ); CPPUNIT_TEST_SUITE_END();};
清单 15 给出清单 14 中代码的输出。
清单 15. 清单 14 中代码的输出
[arpan@tintin] ./a.out. Do some initialization here…FCleanup actions post test execution…. Do some initialization here…FCleanup actions post test execution…!!!FAILURES!!!Test Results:Run: 2 Failures: 2 Errors: 01) test: mystringTest::checkLength (F) line: 26 str.ccassertion failed- Expression: s.size() == 0- String Length Non-Zero2) test: mystringTest::checkValue (F) line: 32 str.ccequality assertion failed- Expected: h- Actual : w- Corrupt String Data
正如在输出中看到的,每次执行单元测试都会显示设置和清除例程消息。
回页首
创建不使用宏的测试套件
可以创建不使用任何辅助宏的测试套件。这两种风格并没有明显的优劣,但是无宏风格的代码更容易调试。要想创建不使用宏的测试套件,应该实例化 CppUnit::TestSuite
,然后在套件中添加测试。最后,把套件本身传递给CppUnit::TextTestRunner
,然后再调用 run
方法。客户端代码很相似,见 清单 16。
清单 16. 创建不使用辅助宏的测试套件
int main (){ CppUnit::TestSuite* suite = new CppUnit::TestSuite("mystringTest"); suite->addTest(new CppUnit::TestCaller<mystringTest>("checkLength", &mystringTest::checkLength)); suite->addTest(new CppUnit::TestCaller<mystringTest>("checkValue", &mystringTest::checkLength)); // client code follows next CppUnit::TextTestRunner runner; runner.addTest(suite); runner.run(); return 0;}
要想理解 清单 16,需要理解 CppUnit 名称空间中的两个类:TestSuite
和 TestCaller
(分别在 TestSuite.h 和 TestCaller.h 中声明)。在执行runner.run()
调用时,对于每个 TestCaller
对象,在 CppUnit 内部调用 runTest
方法,它进而调用传递给 TestCaller<mystringTest>
构造函数的例程。清单 17 中的代码(取自 CppUnit 源代码)说明如何为每个套件调用测试。
清单 17. 执行套件中的测试
voidTestComposite::doRunChildTests( TestResult *controller ){ int childCount = getChildTestCount(); for ( int index =0; index < childCount; ++index ) { if ( controller->shouldStop() ) break; getChildTestAt( index )->run( controller ); }}
TestSuite
类派生自 CppUnit::TestComposite
。
回页首
运行多个测试套件
可以创建多个测试套件并使用 TextTestRunner
在一个操作中运行它们。只需像 清单 16 那样创建每个测试套件,然后使用 addTest
方法把它们添加到 TextTestRunner
中,见清单 18。
清单 18. 使用 TextTestRunner 运行多个套件
CppUnit::TestSuite* suite1 = new CppUnit::TestSuite("mystringTest");suite1->addTest(…);…CppUnit::TestSuite* suite2 = new CppUnit::TestSuite("mymathTest");…suite2->addTest(…);CppUnit::TextTestRunner runner;runner.addTest(suite1);runner.addTest(suite2);…
回页首
定制输出的格式
到目前为止,测试的输出都是由 TextTestRunner
类默认生成的。但是,CppUnit 允许使用定制的输出格式。用于实现这个功能的类之一是CompilerOutputter
(在头文件 CompilerOutputter.h 中声明)。这个类允许指定输出中文件名-行号信息的格式。另外,可以把日志直接保存到文件中,而不是发送到屏幕。清单 19 提供一个把输出转储到文件的示例。注意格式%p:%l
:前者表示文件的路径,后者表示行号。使用这种格式时的典型输出像 /home/arpan/work/str.cc:26 这样。
清单 19. 把测试输出转发到日志文件并采用定制的格式
#include <cppunit/extensions/TestFactoryRegistry.h>#include <cppunit/ui/text/TextTestRunner.h>#include <cppunit/extensions/HelperMacros.h>#include <cppunit/CompilerOutputter.h>int main (){ CppUnit::Test *test = CppUnit::TestFactoryRegistry::getRegistry().makeTest(); CppUnit::TextTestRunner runner; runner.addTest(test); const std::string format("%p:%l"); std::ofstream ofile; ofile.open("run.log"); CppUnit::CompilerOutputter* outputter = new CppUnit::CompilerOutputter(&runner.result(), ofile); outputter->setLocationFormat(format); runner.setOutputter(outputter); runner.run(); ofile.close(); return 0;}
CompilerOutputter
还有很多其他有用的方法,比如可以使用 printStatistics
和printFailureReport
获取它转储的信息的子集。
回页首
更多定制:跟踪测试时间
到目前为止,都是默认使用 TextTestRunner
运行测试。这种方式非常简便:实例化一个 TextTestRunner
类型的对象,在其中添加测试和输出器,然后调用run
方法。现在,我们使用 TestRunner
(TextTestRunner
的超类)和一种称为监听器 的类改变这种运行过程。假设希望跟踪各个测试花费的时间 — 执行性能基准测试的开发人员常常需要这样做。在进一步解释之前,先看一下清单 20。这段代码使用三个类 TestRunner
、TestResult
和 myListener
(派生自TestListener
)。这里仍然使用 清单 10 中的 mystringTest
类。
清单 20. TestListener 类的使用
class myListener : public CppUnit::TestListener {public: void startTest(CppUnit::Test* test) { std::cout << "starting to measure time\n"; } void endTest(CppUnit::Test* test) { std::cout << "done with measuring time\n"; }};int main (){ CppUnit::TestSuite* suite = new CppUnit::TestSuite("mystringTest"); suite->addTest(new CppUnit::TestCaller<mystringTest>("checkLength", &mystringTest::checkLength)); suite->addTest(new CppUnit::TestCaller<mystringTest>("checkValue", &mystringTest::checkLength)); CppUnit::TestRunner runner; runner.addTest(suite); myListener listener; CppUnit::TestResult result; result.addListener(&listener); runner.run(result); return 0;}
清单 21 给出清单 20 的输出。
清单 21. 清单 20 中代码的输出
[arpan@tintin] ./a.outstarting to measure timedone with measuring timestarting to measure timedone with measuring time
myListener
类是 CppUnit::TestListener
的子类。需要覆盖 startTest
和endTest
方法,这两个方法分别在每个测试之前和之后执行。可以通过扩展这些方法轻松地检查各个测试花费的时间。那么,为什么不在设置/清除例程中添加这种功能呢?可以这么做,但是这意味着在每个测试套件的设置/清除方法中会出现重复的代码。
接下来,看看运行器对象,它是 TestRunner
类的实例,它在 run
方法中接收一个 TestResult
类型的参数,并在 TestResult
对象中添加监听器。
最后,输出结果会发生什么变化?TextTestRunner
在运行 run
方法之后显示许多信息,但是 TestRunner
不显示这些信息。我们需要使用输出器对象显示监听器对象在执行测试期间收集的信息。清单 22 显示需要对清单 20 做的修改。
清单 22. 添加输出器以显示测试执行信息
runner.run(result);CppUnit::CompilerOutputter outputter( &listener, std::cerr );outputter.write();
但是等一下:代码还无法编译。CompilerOutputter
的构造函数需要一个 TestResultCollector
类型的对象,而且因为TestResultCollector
本身派生自 TestListener
(关于 CppUnit 类层次结构的详细信息见参考资料),所以需要从 TestResultCollector
派生 myListener
。清单 23 给出可编译的代码。
清单 23. 从 TestResultCollector 派生监听器类
class myListener : public CppUnit::TestResultCollector {…};int main (){ … myListener listener; CppUnit::TestResult result; result.addListener(&listener); runner.run(result); CppUnit::CompilerOutputter outputter( &listener, std::cerr ); outputter.write(); return 0;}
输出见 清单 24。
清单 24. 清单 23 中代码的输出
[arpan@tintin] ./a.outstarting to measure timedone with measuring timestarting to measure timedone with measuring timestr.cc:31:AssertionTest name: checkLengthassertion failed- Expression: s.size() == 0- String Length Non-Zerostr.cc:31:AssertionTest name: checkValueassertion failed- Expression: s.size() == 0- String Length Non-ZeroFailures !!!Run: 0 Failure total: 2 Failures: 2 Errors: 0
回页首
结束语
本文主要讨论了 CppUnit 框架的一些类:TestResult
、TestListener
、TestRunner
、CompilerOutputter
等。CppUnit 是一个独立的单元测试框架,它还提供许多其他功能。CppUnit 中有用于生成 XML 输出的类(XMLOutputter
)和用于以 GUI 模式运行测试的类(MFCTestRunner
和 QtTestRunner
),还提供一个插件接口(CppUnitTestPlugIn
)。一定要查阅 CppUnit 文档来了解它的类层次结构,通过示例了解详细的安装信息。
参考资料
学习
- CppUnit 文档:访问 Sourceforge.com 上的项目页面。
- CppUnit on Wikipedia:Wikipedia 包含关于 CppUnit 的信息以及其他单元测试框架的链接。
- developerWorks 技术活动和网络广播:随时关注最新技术。
- 技术书店:在技术书店浏览关于这些主题和其他技术主题的图书。
获得产品和技术
- 下载 CppUnit:获取 CppUnit 的最新版本。
- IBM 产品评估版:试用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- developerWorks 博客:阅读我们的博客并加入developerWorks 社区。
- 加入 My developerWorks 社区。
- 参与 AIX 和 UNIX 论坛:
- AIX 论坛
- AIX for developers 论坛
- 集群系统管理
- IBMSupport Assistant 论坛
- 性能工具论坛
- 虚拟化论坛
- 更多 AIX 和 UNIX 论坛
转载自IBM技术文章 http://www.ibm.com/developerworks/cn/aix/library/au-ctools2_cppunit/
- 关于CppUnit里面宏的介绍
- CppUnit 介绍
- CppUnit介绍
- CppUnit 介绍
- 收集介绍如何使用CppUnit的网址
- 关于cppunit不支持unicode的解决
- 关于vc里面的宏
- 关于cppunit的安装及运行(linux下)
- 关于CppUnit单元测试的一些编译错误以及改正
- 关于C里面宏替换的问题
- 关于C里面宏替换的问题
- 关于C里面宏替换的问题
- iOS里面Frameworks 的介绍
- iOS里面Frameworks 的介绍
- CPPUnit 的在VC6
- 配置cppunit的方法
- CppUnit的使用步骤
- CppUnit的使用步骤
- Linux shell脚本全面学习 .
- Tegra3 1080p高清播放时电源管理方式和功耗分析
- [实战]3天让Web应用承载拓展1000倍
- 获取所有的插件
- Eclipse使用Axis生成WebService客户端的过程
- 关于CppUnit里面宏的介绍
- Oracle 起步日记(16)——控制文件管理
- 循环使用strrchr要注意的问题
- HBase 和 MongoDB在设计上的区别
- MAC 开发笔记 - Objective-C 语法之selector
- OpenGL纹理映射演示程序代码
- 提升Web应用程序性能的6种基本方式
- PHP unset销毁变量并释放内存
- 临时表vs.表变量以及它们对SQLServer性能的影响