没有合适的资源?快使用搜索试试~ 我知道了~
本教程整理自站长晨光(Morning)的CppUnit源码阅读笔记,CppUnit是自动化单元测试框架的c++实现版本。如何将诸多技术综合运用到一个实际的框架中来,CppUnit为我们提供了一个难易适中的参考范例。在这里,我们可以看到STL、Design Pattern的灵活运用。希望可以通过站长的讲解,使大家能够从中汲取有益的营养。
资源推荐
资源详情
资源评论
名称 CppUnit 源码解读
作者 晨光(Morning)
简介
本教程整理自站长的 CppUnit 源码阅读笔记,CppUnit 是自动化单元测试框架的
c++实现版本。如何将诸多技术综合运用到一个实际的框架中来,CppUnit 为我
们提供了一个难易适中的参考范例。在这里,我们可以看到 STL、Design
Pattern 的灵活运用。希望可以通过站长的讲解,使大家能够从中汲取有益的营
养。
声明
本教程版权为晨光(Morning)所有,未经允许,请勿复制、传播,谢谢。
(http://morningspace.51.net/)
原文地址
http://morningspace.51.net/resource/cppunit/cppunit_anno.html
1 序言
[引言]
记得 2003 年的春节假期,难得有时间可以静下来充充电,于是有了研读 CppUnit 源码的念头。
一来是为了熟悉 CppUnit 的使用环境,而来也是希望通过研读源码汲取有益的东西,这一系列
的文章便是整理自笔者当初的源码阅读笔记。
如何将诸多技术综合运用到一个实际的 framework 中来,笔者以为,CppUnit 为我们提供了
一个难易适中的参考范例。这应该是一个很好的例子,因为它不甚复杂,却汇聚了一个
framework 所必需的某些设计思想以及实现技巧。在这里,我们可以看到 STL 的实际使用
(包括一些简单的 traits 技法),Design Pattern 的灵活运用(比如:
Composite,Factory,Decorator,Singleton,Observer 等)。
当然,也应该指出,由于 CppUnit 还在不断改进中,其代码中未免还有“败笔”及不尽如人意之
处。但是,瑕不掩瑜,并且从中我们也可以感受到一个成熟框架的演进过程。
由于有过一点 framework 的设计经验和体会,笔者在阅读 CppUnit 源码的过程中,时常能有
共鸣,并且对于框架的设计者在某些细节的处理方法,也深以为然,偶尔也有“英雄所见略同”
的感叹。希望可以通过笔者的讲解,使大家也能够同样有亲历之感。
[CppUnit 的简单身世]
CppUnit 是 xUnit 系列中的 c++实现版本,它是从 JUnit 移植过来的,第一个移植版本由
Michael Feathers 完成,相关信息可以
在 http://www.xprogramming.com/software.htm 找到。它是操作系统相关的,随后,
Jerome Lacoste 将之移植到了 Unix/Solaris,在上述连接中也能找到该版本的相关信息。
CppUnit 项目就是基于这些版本建立起来的。有关 CppUnit 的讨论可以在 http://c2.com/
cgi/wiki?CppUnit 找到,在那里你还可以找到 CppUnit 先前的版本以及许多其它操作系统环
境下的移植版本。这个库受 GNU LGPL(Lesser General Public License)的保护。作者包
括:Eric Sommerlade (sommerlade@gmx.net),Michael Feathers
(mfeathers@objectmentor.com),Jerome Lacoste (lacostej@altern.org),J.E.
Ho7mann ,Baptiste Lepilleur ,Bastiaan Bakker ,Steve Robbins
这里所选用的是 CppUnit 1.8.0 版,你可以从 http://sourceforge.net/projects/cppunit/下
载到最新版本。
[CppUnit 的总体构成]
作为一个完整的 CppUnit framework,虽然源码所在的实际路径可能不尽相关,但从逻辑上
讲它们被划为如下几个部分:
core:CppUnit 的核心部分
output:掌管结果输出
helper:一些辅助类
extension:作为单元测试的延伸,对 CppUnit core 部分的扩展(比如:常规测试,
重复测试)
listener:监视测试进程和测试结果
textui:一个运行单元测试的文本环境
portability:提供针对不同平台的移植设置
上述所有的内容均被置于 CppUnit 名字空间之内。
[几点说明]
本文主要内容依据 CppUnit 源码而来,部分内容还来自于源码自身所附的注释、
ChangeLog 等
本文只作源码解读,至于 xUnit 家族的相关背景及基本知识笔者不准备叙述,读者可
以参看相关文章
对于文中所涉及的 Design Pattern,Refactoring,STL 等相关知识,请读者参看相
关资料。
除了文章本身,文中所列源码,也夹带了 morning 的一些注释,用以进一步说明代码
意图,注释中方括号内为 morning 的疑问
为了节省篇幅、简化内容、突出主题,文中未列出全部代码,而是有选择的给出部分
代码
由于工作的缘故,撰写这一系列的文章是陆续进行的,因此文字斟酌、行文的前后一
致性方面不甚考究,在此请诸位见谅。如有必要且时间允许,morning 将会对此作一
完整的整理。
2 核心部分(Core)
基本测试类
在 CppUnit 中,有一个贯穿始终的最基本的 pattern,那便是 Composite Pattern。在 GoF
中对该 pattern 有如下描述:将对象组合成树形结构以表示“部分-整体”的层次结构。
Composite 使得用户对单个对象和组合对象的使用具有一致性。在 CppUnit 的框架中,测试
类分为两种,某些测试类代表单个测试,比如稍后讲到的 TestCase(测试用例),另一些则
由若干测试类共同构成,比如稍后讲到的 TestSuite(测试包)。彼此相关的 TestCase 共同
构成一个 TestSuite,而 TestSuite 也可以嵌套包含。两者分别对应 Composite Pattern 中
的 Leaf 和 Composite。
[Test]
相关文件:Test.h
这是所有测试类的抽象基类,规定了所有测试类都应该具有的行为,对应于 Composite
Pattern 中的 Component,除了标准的 virtual dtor 外,还定义了四个纯虚函数:
// 运行测试内容,并利用传入其内的 TestResult 搜集测试结果,类似 Component 的
Operation 操作
virtual void run (TestResult *result) = 0;
// 返回当前包含的测试对象的个数,若为 TestCase,则返回 1。
virtual int countTestCases () const = 0;
// 返回测试的名称,每个测试都有一个名称,就像是标识,用以查找或显示
virtual std::string getName () const = 0;
// 本测试的简短描述,用于调试输出。
// 对测试的描述除了名称外,可能还有其他信息,
// 比如:一个名为“complex_add”的测试包可能被描述成“suite complex_add”
virtual std::string toString () const = 0;
[TestFixture]
相关文件:TestFixture.h
该类也是抽象类,用于包装测试类使之具有 setUp 方法和 tearDown 方法。利用它,可以为
一组相关的测试提供运行所需的公用环境(即所谓的 <xture)。要实现这一目的,你需要:
从 TestFixture 派生一个子类(事实上,一般的做法是从 TestCase 派生,这样比较
方便,具体见后)
定义实例变量(instance variables)以形成 <xture
重载 setUp 初始化 <xture 的状态
重载 tearDown 在测试结束后作资源回收工作
此外,作为完整的测试类,还要定义一些执行具体测试任务的测试方法,然后使用 TestCaller
进行测试。关于 TestCaller,在 helper 部分将会讲到。
因为每个测试对象运行在其自身的 <xture 中,所以测试对象之间不会有副作用(side
e7ects),而测试对象内部的测试方法则共同使用同一个 <xture。
来看一下 TestFixture 的定义,除了标准的 virtual dtor 外,还定义了两个纯虚函数:
// 在运行测试之前设置其上下文,即 fixture
// 一般而言 setUp 更为重要些,除非实例变量创建于 heap 中,否则其资源的回收就无需手工处理了
virtual void setUp() {};
// 在测试运行结束之后进行资源回收
virtual void tearDown() {};
[TestCase]
相关文件:TestCase.h,TestCase.cpp
派生自 Test 和 TestFixture(多重继承),兼具两者特性,用于实现一个简单的测试用例。你
所要做的就是派生该类,并重载 runTest 方法。不过通常你不必如此,而是使用 TestCaller
结合 TestFixture 的方法,这样很方便。当你发现 TestCaller 无法满足,你需要重写一个功能
近似的类时,再使用 TestCase 也不迟。关于 TestCaller,在 helper 部分将会讲到。
TestCase 中最重要的方法是 run 方法,来看一下代码,并请留意 morning 的注释:
void TestCase::run( TestResult *result )
{
// 不必关心 startTest 的具体行为,在讲到 TestResult 时自然会明白
// 末尾的 endTest 亦是如此
result->startTest(this);
try {
// 设置 fixture,具体内容需留待派生类解决
// 可能有异常抛出,处理方式见后
setUp();
// runTest 具有 protected 属性,是真正执行测试的函数
// 但具体行为需留待派生类解决
try {
runTest();
}
// 在运行测试时可能会抛出异常,以下是异常处理
catch ( Exception &e ) {
// Prototype Pattern 的一个应用
// e 是临时对象,addFailure 调用之后即被销毁,所以需要创建一个副本
Exception *copy = e.clone();
result->addFailure( this, copy );
}
catch ( std::exception &e ) {
// 异常处理的常用方法——转意
result->addError( this, new Exception( e.what() ) );
}
catch (...) {
// 截获其余未知异常,一网打尽
Exception *e = new Exception( "caught unknown exception" );
result->addError( this, e );
}
// 资源回收
try {
tearDown();
}
catch (...) {
result->addError( this, new Exception( "tearDown() failed" ) );
}
}
catch (...) {
result->addError( this, new Exception( "setUp() failed" ) );
}
result->endTest( this );
}
可以看到,run 方法定义了一个测试类运行的基本行为及其顺序:
setUp:准备
runTest:开始
tearDown:结束
而 TestCase 作为抽象类无法确定测试的具体行为,因此需要留待派生类解决,这就是
Template Method Pattern。事实上,该 pattern 在 framework 中是很常见的。因此一个
完整测试的简单执行方法是,从 TestCase 派生一个类,重载相关方法,并直接调用 run 方法
(正如 TestFixture 中所提到的)。
有意思的是,TestCase 中还有 run 的另一个版本,它没有形参,而是创建一个缺省的
TestResult,然后调用前述 run 方法。不过好像没怎么用到,大概是先前调试时未及清理的垃
圾代码,也难怪会有“FIXME: what is this for?”这样的注释了。
剩余63页未读,继续阅读
资源评论
七仔的星期六
- 粉丝: 1
- 资源: 6
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功