测试驱动开发的原则:
先写测试代码,然后编写符合测试的代码。至少做到完成部分代码后,完成对应的测试代码;
测试代码不需要覆盖所有的细节,但应该对所有主要的功能和可能出错的地方有相应的测试用例;
发现 bug,首先编写对应的测试用例,然后进行调试;
不断总结出现 bug 的原因,对其他代码编写相应测试用例;
每次编写完成代码,运行所有以前的测试用例,验证对以前代码影响,把这种影响尽早消除;
不断维护测试代码,保证代码变动后通过所有测试;
在编码前:他可以强迫你对需求进行详细的分析。
在编码时:他可以使你对over coding保持警觉。
在重构时:可以确保新的设计能够兼容旧版本的功能。
在团队开发时:可以确保自己的单元是无误的。
CppUnit的原理
在 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<MathTest> (
"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 中。下面就是通常的使用方式(注意红色字体):
/// MathTest.h
#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 ();
// 可以添加新的测试函数
};
/// MathTest.cpp
// 把这个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 );
}
/// main.cpp
// 不用再包含所有TestFixture子类的头文件
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
// 如果不更改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();
}
/// mainanother.cpp
另外的一种写法。其实和上面大同小异,只是生成的对象是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 可能引用了别的类或者别的函数,为了隔离其他部分代码的影响,应该在源文件中临时定义一些桩程序,模拟这些类或者函数。这些代码可以通过宏定义在测试项目中有效,而在被测试的项目中无效。
CppUnit的组成结构
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 其他