CppUnit 使用指南
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 对象 [LSLiu Sam1] 。而最终运行整个应用程序的测试代码的时候,可能需要同时运行对一个 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 A sserter
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 A utoRegisterSuite
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
从 http://voxel.dl.sourceforge.net/sourceforge/cppunit/cppunit-1.10.2.tar.gz 下载一个安装包,然后解压到指定目录,文章后面用 c指代解压目录。
打开 $CPPUNIT/src/ CppUnitLibraries.dsw文件。
在 VC IDE中打开 Build菜单,选择‘ Batch Build…’
在 Batch Build菜单中,选择所有的编译项目。
然后在 /lib目录你会找到所有编译生成的文件。
在 Tools/Options菜单中分别设置 include files , libraries files , source file 。
准备开始新项目,创建一个 new console application ,选择‘ a simple application ’模板。在 project setting 中:
Tab ‘ C ++’,多选框‘ Code Generation ’, 针对 debug和 release分别设置为‘ Multithreaded DLL ’和‘ Debug Multithreaded DLL ’。
Tab ‘ C++’,多选框‘ C++ langage ’,选择 All Configurations,选上‘ 'enable Run-Time Type Information (RTTI)’。
Tab ‘ Link’,在‘ Object/library modules’中,针对 debug和 release分别加入 cppunitd.lib和 cppunit.lib。
Tab ‘ post-build step’,选择 All Configurations,在‘ post-build command(s)’中增加一个命令‘ $(TargetPath)’。这样 CppUnit在项目编译完后会进行自动的测试。
把 main函数所在的文件的代码改为如下:
#include "stdafx.h"
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/ text /TestRunner.h>
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 ;
}
按 Ctrl + F5 ,出现以下类似语句:
Compiling...
UnitReTest.cpp
Linking...
Unit testing...
OK (0)
UnitReTest.exe - 0 error(s), 0 warning
到此为止,大功告成。
Ps :若出现 stlportxxxx.dll 文件找不到的情况,是因为 CppUnit 用到了 stlport ,请下载最新的版本: http://www.stlport.org/archive/STLport-4.6.2.tar.gz ,再根据 stlport 的安装和配置手册设置后就可以了。
作者 Blog : http://blog.csdn.net/casualgame/
[LSLiu Sam1] 一个测试用例对应一个 CppUnit::TestCaller对象。