Ø 先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
Ø 测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
Ø 发现 bug,首先编写对应的测试用例,然后进行调试;
Ø 不断总结出现 bug 的原因,对其他代码编写相应测试用例;
Ø 每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
Ø 不断维护测试代码,保证代码变动后通过所有测试;
Ø 在编码前:他可以强迫你对需求进行详细的分析。
Ø 在编码时:他可以使你对over coding保持警觉。
Ø 在重构时:可以确保新的设计能够兼容旧版本的功能。
Ø 在团队开发时:可以确保自己的单元是无误的。
在 CppUnit 中,一个或一组测试用例的测试对象被称为 Fixture(设施,下文为方便理解尽量使用英文名称)。Fixture 就是被测试的目标,可能是一个对象或者一组相关的对象,甚至一个函数。
有了被测试的 fixture,就可以对这个 fixture 的某个功能、某个可能出错的流程编写测试代码,这样对某个方面完整的测试被称为TestCase(测试用例)。通常写一个 TestCase 的步骤包括:
1. 对 fixture 进行初始化,及其他初始化操作,比如:生成一组被测试的对象,初始化值;
2. 按照要测试的某个功能或者某个流程对 fixture 进行操作;
3. 验证结果是否正确;
4. 对 fixture 的及其他的资源释放等清理工作。
对 fixture 的多个测试用例,通常(1)(4)部分代码都是相似的,CppUnit 在很多地方引入了 setUp 和 tearDown 虚函数。可以在 setUp 函数里完成(1)初始化代码,而在 tearDown 函数中完成(4)代码。具体测试用例函数中只需要完成(2)(3)部分代码即可,运行时 CppUnit 会自动为每个测试用例函数运行 setUp,之后运行 tearDown,这样测试用例之间就没有交叉影响。
撰写TestCase必须注意以下几点:
Ø 可以自动执行,不用人手操作。
Ø 自动返回测试结果。
Ø 绝对的独立,不能与其他TestCase有任何联系。就算测试同一个函数的不同功能也需要分开。每个TestCase可以说是一个孤岛。
对 fixture 的所有测试用例可以被封装在一个 CppUnit::TestFixture 的子类(命名惯例是[ClassName]Test)中。然后定义这个fixture 的 setUp 和 tearDown 函数,为每个测试用例定义一个测试函数(命名惯例是 testXXX)。下面是个简单的例子:
class MathTest : public CppUnit::TestFixture {
protected:
int m_value1, m_value2;
public:
MathTest() {}
// 初始化函数
void setUp () {
m_value1 = 2;
m_value2 = 3;
}
// 测试加法的测试函数
void testAdd () {
// 步骤(2),对 fixture 进行操作
int result = m_value1 + m_value2;
// 步骤(3),验证结果是否争取
CPPUNIT_ASSERT( result == 5 );
}
// 没有什么清理工作没有定义 tearDown.
}
在测试函数中对执行结果的验证成功或者失败直接反应这个测试用例的成功和失败。CppUnit 提供了多种验证成功失败的方式:
CPPUNIT_ASSERT(condition) // 确信condition为真
CPPUNIT_ASSERT_MESSAGE(message, condition) // 当condition为假时失败, 并打印message
CPPUNIT_FAIL(message) // 当前测试失败, 并打印message
CPPUNIT_ASSERT_EQUAL(expected, actual) // 确信两者相等
CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual) // 失败的同时打印message
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta) // 当expected和actual之间差大于delta时失败
要把对 fixture 的一个测试函数转变成一个测试用例,需要生成一个 CppUnit::TestCaller 对象。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 fixture 的多个测试函数,甚至多个 fixture 的测试用例。CppUnit 中把这种同时运行的测试案例的集合称为 TestSuite。而 TestRunner 则运行测试用例或者 TestSuite,具体管理所有测试用例的生命周期。目前提供了 3 类TestRunner,包括:
Ø CppUnit::TextUi::TestRunner // 文本方式的TestRunner
Ø CppUnit::QtUi::TestRunner // QT方式的TestRunner
Ø CppUnit::MfcUi::TestRunner // MFC方式的TestRunner
下面是一个TestRunner的例子:
CppUnit::TextUi::TestRunner runner;
CppUnit::TestSuite *suite= new CppUnit::TestSuite();
// 添加一个测试用例
suite->addTest(new CppUnit::TestCaller
"testAdd", testAdd));
// 指定运行TestSuite
runner.addTest( suite );
// 开始运行, 自动显示测试进度和测试结果
runner.run( "", true ); // Run all tests and wait
按照上面的方式,如果要添加新的测试用例,需要把每个测试用例添加到 TestSuite 中,而且添加新的 TestFixture 需要把所有头文件添加到 main.cpp 中,比较麻烦。为此 CppUnit 提供了 CppUnit::TestSuiteBuilder,CppUnit::TestFactoryRegistry 和一堆宏,用来方便地把 TestFixture 和测试用例注册到 TestSuite 中。下面就是通常的使用方式(注意红色字体):
#include "cppunit/extensions/HelperMacros.h"
class MathTest : public CppUnit::TestFixture {
// 声明一个TestSuite
CPPUNIT_TEST_SUITE( MathTest );
// 添加测试用例到TestSuite, 定义新的测试用例需要在这儿声明一下
CPPUNIT_TEST( testAdd );
// TestSuite声明完成
CPPUNIT_TEST_SUITE_END();
// 其余不变
protected:
int m_value1, m_value2;
public:
MathTest() {}
// 初始化函数
void setUp ();
// 清理函数
void tearDown();
// 测试加法的测试函数
void testAdd ();
// 可以添加新的测试函数
};
// 把这个TestSuite注册到名字为"alltest"的TestSuite中, 如果没有定义会自动定义
// 也可以CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );注册到全局的一个未命名的TestSuite中.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MathTest, "alltest" );
// 下面不变
void MathTest::setUp()
{
m_value1 = 2;
m_value2 = 3;
}
void MathTest::tearDown(){
}
void MathTest::testAdd(){
int result = m_value1 + m_value2;
CPPUNIT_ASSERT( result == 5 );
}
// 不用再包含所有TestFixture子类的头文件
#include
#include
// 如果不更改TestSuite, 本文件后期不需要更改.
int main()
{
CppUnit::TextUi::TestRunner runner;
// 从注册的TestSuite中获取特定的TestSuite, 没有参数获取未命名的TestSuite.
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry("alltest");
//若没有注册名字,在MathTest.cpp文件中只是调用了CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );那就可以写成
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
// 添加这个TestSuite到TestRunner中
runner.addTest( registry.makeTest() );
// 运行测试
runner.run();
}
另外的一种写法。其实和上面大同小异,只是生成的对象是Test而不是registry了。
int main(int argc, char* argv[])
{
// Get the top level suite from the registry
CppUnit::Test *suite = CppUnit::TestFactoryRegistry::getRegistry().makeTest();
// Adds the test to the list of test to run
CppUnit::TextUi::TestRunner runner;
runner.addTest( suite );
// Change the default outputter to a compiler error format outputter
runner.setOutputter( new CppUnit::CompilerOutputter( &runner.result(),
std::cerr ) );
// Run the tests.
bool wasSucessful = runner.run();
// Return error code 1 if the one of test failed.
return wasSucessful ? 0 : 1;
}
这样添加新的测试用例(TestFixture)只需要在类定义的开始声明一下即可。
通常包含测试用例代码和被测试对象是在不同的项目中。应该在另一个项目(最好在不同的目录)中编写 TestFixture,然后把被测试的对象包含在测试项目中。
对某个类或者某个函数进行测试的时候,这个 TestFixture 可能引用了别的类或者别的函数,为了隔离其他部分代码的影响,应该在源文件中临时定义一些桩程序,模拟这些类或者函数。这些代码可以通过宏定义在测试项目中有效,而在被测试的项目中无效。
2 核心部分(Core)
2.1 基本测试类
2.1.1 Test
2.1.2 TestFixture
2.1.3 TestCase
2.1.4 TestSuite
2.2 测试结果记录
2.2.1 SynchronizedObject
2.2.2 TestListener
2.2.3 TestResult
2.3 错误处理
2.3.1 TestFailure
2.3.2 SourceLine
2.3.3 Exception
2.3.4 NotEqualException
2.4 断言
2.4.1 Asserter
2.4.2 TestAssert
3 输出部分(Output)
3.1 基础部件
3.1.1 Outputter
3.1.2 TestResultCollector
3.2 衍生类
3.2.1 TextOutputter
3.2.2 CompilerOutputter
3.2.3 XmlOutputter
4 辅助部分(Helper)
4.1 创建机制
4.1.1 TypeInfoHelper
4.1.2 TestFactory
4.1.3 TestFactoryRegistry,NamedRegistries
4.1.4 TestSuiteFactory
4.1.5 TestSuiteBuilder
4.1.6 TestCaller
4.1.7 AutoRegisterSuite
4.2 HelperMacros
5 扩展部分(Extension)
5.1 TestDecorator
5.2 RepeatedTest
5.3 Orthodox
5.4 TestSetUp
6 兼听者部分(Listener)
6.1 TestSucessListener
6.2 TextTestProgressListener
6.3 TextTestResult
7 界面部分(TextUI)
7.1 TestRunner
8 移植(Portability)
8.1 OStringStream
8.2 其他在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外,还定义了四个纯虚函数:
[TestFixture]
相关文件:TestFixture.h
该类也是抽象类,用于包装测试类使之具有setUp方法和tearDown方法。利用它,可以为一组相关的测试提供运行所需的公用环境(即所谓的fixture)。要实现这一目的,你需要:
此外,作为完整的测试类,还要定义一些执行具体测试任务的测试方法,然后使用TestCaller进行测试。关于TestCaller,在helper部分将会讲到。
因为每个测试对象运行在其自身的fixture中,所以测试对象之间不会有副作用(side effects),而测试对象内部的测试方法则共同使用同一个fixture。
来看一下TestFixture的定义,除了标准的virtual dtor外,还定义了两个纯虚函数:
[TestCase]
相关文件:TestCase.h,TestCase.cpp
派 生自Test和TestFixture(多重继承),兼具两者特性,用于实现一个简单的测试用例。你所要做的就是派生该类,并重载runTest方法。不 过通常你不必如此,而是使用TestCaller结合TestFixture的方法,这样很方便。当你发现TestCaller无法满足,你需要重写一个 功能近似的类时,再使用TestCase也不迟。关于TestCaller,在helper部分将会讲到。
TestCase中最重要的方法是run方法,来看一下代码,并请留意morning的注释:
可以看到,run方法定义了一个测试类运行的基本行为及其顺序:
而TestCase 作为抽象类无法确定测试的具体行为,因此需要留待派生类解决,这就是Template Method Pattern。事实上,该pattern在framework中是很常见的。因此一个完整测试的简单执行方法是,从TestCase派生一个类,重载相 关方法,并直接调用run方法(正如TestFixture中所提到的)。
有 意思的是,TestCase中还有run的另一个版本,它没有形参,而是创建一个缺省的TestResult,然后调用前述run方法。不过好像没怎么用 到,大概是先前调试时未及清理的垃圾代码,也难怪会有“FIXME: what is this for?”这样的注释了。
TestCase有两个ctor:
后者主要用于TestCaller,因为在使用TestCaller时,需要一个default ctor[奇怪,编译器按理可以自动生成default ctor,这么做岂不画蛇添足,将该函数去掉,照样可以运行]
此外,TestCase将copy ctor和operator=声明为private属性,以防止误用。
[TestSuite]
相关文件:TestSuite.h,TestSuite.cpp
一 组相互关联的测试用例,构成了一个测试包,这就是TestSuite,也就是Composite Pattern中的Composite。和TestCase一样,也派生自Test,只是没有fixture特性。除了测试类的名称外,在 TestSuite中还维护了一个测试对象数组,它被声明为private属性:
来看一下TestSuite的run方法是如何实现的,并请留意morning的注释:
关 于TestResult及其shouldStop方法,稍后会讲到。不过此处的break,到也算是活用Composite Pattern的一个简单范例。从效率的角度考虑,当确信不必再执行后续的test时,即可直接返回,而不是照葫芦画瓢,简单的调用一下test的run 方法。
既然TestResult派生自Test,那么countTestCases又是如何实现的呢:
至于addTest,自然是不能少的,它对应于Composite的Add方法:
不过请注意,addTest方法并未出现于抽象类Test中,关于这类设计上的权衡在GoF中,Composite Pattern一节有专门的论述。
TestSuite管理着其下所属诸测试对象的生命周期,在dtor中,它会调用deleteContents方法:
此外,TestSuite还为外部访问其所属测试对象提供了接口,因为返回值是const &类型的,所以是read-only的: