一、单元测试与CPPUNIT简介
几乎每个开发人员都写过测试代码,但是往往这些代码不系统,也没有良好的管理,同时在测试代码编写过程中也有很多重复的劳动,比较繁琐。在一个软件开发过程中,往往会进行很多修改,迭代开发的模型随处可见,如何验证程序的功能、性能和结构是否符合要求是一项重要的工作。
单元测试是属于白盒测试和结构性测试,一般由开发人员开展,当然如果有好的测试工具支持,测试人员甚至最终用户都可以参与。单元测试框架是编写和运行单元测试的软件工具,用来构建测试、运行测试、报告测试结果。对于c/c++开发,比较著名的收费单元测试工具是C++ Test,免费开源的则是CPPUNIT。
CPPUNIT是基于 LGPL 的开源项目,最初版本移植自 JUNIT ,是一个非常优秀的开源测试框架。CPPUNIT和 JUNIT 一样主要思想来源于极限编程。主要功能就是对单元测试进行管理,并可进行自动化测试。CPPUNIT设计遵循很多设计模式,代码结构也相对好理解。
本文将首先讲述CPPUNIT的使用(linux环境),然后着重深入分析CPPUNIT框架的代码(覆盖了CPPUNIT的所有主干代码),对于想改造CPPUNIT或者程序设计上学习借鉴都起到抛砖引玉的作用。
二、CPPUNIT安装、使用和框架
1、安装
CPPUNIT的主页是http://sourceforge.net/projects/cppunit/,从这里可以获取它的源代码。安装过程也是很标准的,如下:
tar zxvf cppunit-1.12.0.tar.gz
cd cppunit-1.12.0
./configure
make
make install
备注:CPPUNIT默认会把头文件和函数库分别安装到/usr/local/include和/usr/local/lib(除非在configure的时候指定prefix),所以编写测试程序的时候需要能搜索/usr/local/include和/usr/local/lib目录,否则会出错。
2、使用
在CPPUNIT的主目录下有example/simple下的例子代码Main.cpp和ExampleTestCase.h/cpp,
只要这三个文件即可构造一个CPPUNIT的测试程序。
g++ -o mytest Main.cpp ExampleTestCase.cpp -lcppunit -ldl
一般通过派生TestFixture来编写用户自己的测试类,对于一个实际的测试程序,可能会有很多独立的测试类,因此可以仿照ExampleTestCase.h/cpp编写更多的测试类,而Main.cpp是不用变的,然后在编译(实际上往往写makefile)的时候加上那些源文件即可。
g++ -o mytest Main.cpp MyTestCase1.cpp MyTestCase2.cpp MyTestCase3.cpp ..... -lcppunit -ldl
3、框架
本节把CPPUNIT的框架分为三个部分进行简单介绍。
1)测试对象族
CPPUNIT的测试对象的类关系图
Test: 所有测试对象类的抽象基类,主要是定义run方法和统计子对象个数和查找遍历子对象的方法
TestFixture: 该类非常简单,只定义了两个方法setUp和tearDown,作为测试对象的准备和拆除方法,一般用户编写的测试类都直接继承它
TestComposite、TestLeaf: 根据设计模式中组合模式而设计的两个类,都继承自Test
TestSuite: 具体化了TestComposite的内容存储方式、添加子对象接口等等。该类对象包含了若干测试对象,作为测试对象的容器,而且可以嵌套。
TestRunner: 控制测试对象的构造和测试对象执行的类
TestCase: 定义了一个测试对象要实现的具体接口,同时继承TestFixture的setUp和tearDown
接口
TestCaller: 使用了设计模式中的策略模式,作为测试对象的最终封装类,提供了测试运行的策略,在测试执行中扮演了重要的角色。它是一个模板类。
2)信息收集与显示族
CPPUNIT的测试信息收集与显示的类关系图
Outputter: 是所有测试输出类的抽象基类,定义了write方法
CompilerOutputter: 以编译器信息类似方式输出测试信息,使用TestResultCollector获取测试信息
TextOutputter: 以文本流的方式输出测试信息,同样使用TestResultCollector获取测试信息
TestListener: 以设计模式中观察者模式定义了Observer所应该具有的从TestResult获取测试步骤信息的方法
TestSuccessListener: 实现了TestListener接口,同时继承了SynchronizedObject了从而具有线程安全性
SynchronizedObject: 该类实现了lock和unlock操作
ExclusiveZone: 使用SynchronizedObject进行了临界区的加锁和解锁操作
TestResult: 这个测试信息的收集者,在观察者模式中扮演Subject角色,是它把测试的各个步骤的信息通知到所有Listener对象的。
3)测试对象管理族
CPPUNIT测试对象管理类关系图
TestFactory: 运用了设计模式中工厂设计模式,这里只定义了一个makeTest方法,是一个抽象基类
TestSuiteFactory: 该类继承自TestFactory,而且是模板类,是生成TestSuite对象的工厂
TestFactoryRegistry: 管理TestFactory对象的类(这里继承自TestFactory个人感觉有点不太恰当)
AutoRegisterSuite: 模板类,自动把特定的TestSuiteFactory对象注册到TestFactoryRegistry对象
TestSuiteBuilderContextBase、TestSuiteBuilderContext: 用于构建测试对象的类,详细见代码分析部分。
三、剖析CPPUNIT
下面以CPPUNIT 1.12.0版本(当前最新版本)源码为对象,把CPPUNIT程序包中的example程序作为剖析对象,这个程序也是我们使用CPPUNIT的经典结构(以下红色的代码片段起强调作用,红色的注释是额外添加的以增强理解)
1、预编译前C++源代码
程序main函数
int main( int argc, char* argv[] )
{
// Create the event manager and test controller
CPPUNIT_NS::TestResult controller;
// Add a listener that colllects test result
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );
// Add a listener that print dots as test run.
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );
// Add the top suite to the test runner
CPPUNIT_NS::TestRunner runner;
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
runner.run( controller );
// Print test in a compiler compatible format.
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
outputter.write();
return result.wasSuccessful() ? 0 : 1;
}
ExampleTestCase.h
class ExampleTestCase : public CPPUNIT_NS::TestFixture
{
//开始构造一个TestSuite,名字就是ExampleTestCase
CPPUNIT_TEST_SUITE( ExampleTestCase );
//添加一个TestCase(测试方法)
CPPUNIT_TEST( example );
//添加一个TestCase(测试方法)
CPPUNIT_TEST( anotherExample );
//添加一个TestCase(测试方法)
CPPUNIT_TEST( testAdd );
//添加一个TestCase(测试方法)
CPPUNIT_TEST( testEquals );
//构造TestSuite完毕
CPPUNIT_TEST_SUITE_END();
protected:
double m_value1;
double m_value2;
public:
void setUp();
protected:
void example();
void anotherExample();
void testAdd();
void testEquals();
};
//取出构造的TestSuite,加入到全局的管理目录
CPPUNIT_TEST_SUITE_REGISTRATION( ExampleTestCase );
ExampleTestCase.cpp
void ExampleTestCase::example()
{
//CPPUNIT_*开头的是一些断言的宏,如果断言失败则抛出异常
//使用的时候可以参考TestAssert.H头文件
CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, 1.1, 0.05 );
CPPUNIT_ASSERT( 1 == 0 );
CPPUNIT_ASSERT( 1 == 1 );
}
void ExampleTestCase::anotherExample()
{
CPPUNIT_ASSERT (1 == 2);
}
void ExampleTestCase::setUp()
{
m_value1 = 2.0;
m_value2 = 3.0;
}
void ExampleTestCase::testAdd()
{
double result = m_value1 + m_value2;
CPPUNIT_ASSERT( result == 6.0 );
}
void ExampleTestCase::testEquals()
{
long* l1 = new long(12);
long* l2 = new long(12);
CPPUNIT_ASSERT_EQUAL( 12, 12 );
CPPUNIT_ASSERT_EQUAL( 12L, 12L );
CPPUNIT_ASSERT_EQUAL( *l1, *l2 );
delete l1;
delete l2;
CPPUNIT_ASSERT( 12L == 12L );
CPPUNIT_ASSERT_EQUAL( 12, 13 );
CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 );
}
2、预编译后代码
main函数没变化,ExampleTestCase.h/cpp由于包含较多宏,所以变化较大(红色代码是宏展开后的代码),如下所示
ExampleTestCase.cpp(include "ExampleTestCase.h")
class ExampleTestCase : public CppUnit::TestFixture
{
public:
typedef ExampleTestCase TestFixtureType;
private:
//主要是利用C++的RTTI信息取得用户写的TestFixture类的类名字
static const CppUnit::TestNamer &getTestNamer__()
{
static CppUnit::TestNamer testNamer( typeid(ExampleTestCase) );
return testNamer;
}
public:
typedef CppUnit::TestSuiteBuilderContext<TestFixtureType> TestSuiteBuilderContextType;
static void addTestsToSuite( CppUnit::TestSuiteBuilderContextBase &baseContext )
{ //把每个测试方法用TestCaller使用具体的TestFixture类型封装起来,加入
//TestSuite中
TestSuiteBuilderContextType context( baseContext );
//生成TestCaller封装测试代码,加入TestSuite
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "example"), &TestFixtureType::examp
le, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "anotherExample"), &TestFixtureType
::anotherExample, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testAdd"), &TestFixtureType::testA
dd, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testEquals"), &TestFixtureType::te
stEquals, context.makeFixture() ) ) );
}
//注意,这里suite()是static类型方法
static CppUnit::TestSuite *suite()
{
const CppUnit::TestNamer &namer = getTestNamer__();
std::auto_ptr<CppUnit::TestSuite> suite( new CppUnit::TestSuite( namer.getFixtureName() ));
CppUnit::ConcretTestFixtureFactory<TestFixtureType> factory;
CppUnit::TestSuiteBuilderContextBase context( *suite.get(), namer, factory );
TestFixtureType::addTestsToSuite( context );
return suite.release();
}
private:
typedef int CppUnitDummyTypedefForSemiColonEnding__;
protected:
double m_value1;
double m_value2;
public:
//setUp和tearDown是TestFixture的虚拟函数,子类可以不实现。
void setUp();
//void tearDown();
protected:
//以下是用户自己编写的测试方法,至于为什么返回类型都是void,而且无参数
//,否则编译会不通过,在后面分析TestCaller的时候会讲到原因。
void example();
void anotherExample();
void testAdd();
void testEquals();
};
//在AutoRegisterSuite实例对象的构造函数中完成了TestSuite的登记操作
static CppUnit::AutoRegisterSuite< ExampleTestCase > autoRegisterRegistry__4;
void ExampleTestCase::example()
{
//下面这些方法,当断言条件不成立的时候则抛出Exception,Exception中有断言的表达
//式和代码位置信息,这个Exception由外层处理并记录到TestResult
( CppUnit::assertDoubleEquals( (1.0), (1.1), (0.05), CppUnit::SourceLine( "ExampleTestCase.cpp", 8 ), "" ) );
( CppUnit::Asserter::failIf( !(1 == 0), CppUnit::Message( "assertion failed", "Expression: " "1 == 0"), CppUnit::SourceLine( "Ex
ampleTestCase.cpp", 9 ) ) );
( CppUnit::Asserter::failIf( !(1 == 1), CppUnit::Message( "assertion failed", "Expression: " "1 == 1"), CppUnit::SourceLine( "Ex
ampleTestCase.cpp", 10 ) ) );
}
void ExampleTestCase::anotherExample()
{
( CppUnit::Asserter::failIf( !(1 == 2), CppUnit::Message( "assertion failed", "Expression: " "1 == 2"), CppUnit::SourceLine( "Ex
ampleTestCase.cpp", 16 ) ) );
}
void ExampleTestCase::setUp()
{
m_value1 = 2.0;
m_value2 = 3.0;
}
void ExampleTestCase::testAdd()
{
double result = m_value1 + m_value2;
( CppUnit::Asserter::failIf( !(result == 6.0), CppUnit::Message( "assertion failed", "Expression: " "result == 6.0"), CppUnit::S
ourceLine( "ExampleTestCase.cpp", 28 ) ) );
}
void ExampleTestCase::testEquals()
{
long* l1 = new long(12);
long* l2 = new long(12);
( CppUnit::assertEquals( (12), (12), CppUnit::SourceLine( "ExampleTestCase.cpp", 37 ), "" ) );
( CppUnit::assertEquals( (12L), (12L), CppUnit::SourceLine( "ExampleTestCase.cpp", 38 ), "" ) );
( CppUnit::assertEquals( (*l1), (*l2), CppUnit::SourceLine( "ExampleTestCase.cpp", 39 ), "" ) );
delete l1;
delete l2;
( CppUnit::Asserter::failIf( !(12L == 12L), CppUnit::Message( "assertion failed", "Expression: " "12L == 12L"), CppUnit::SourceL
ine( "ExampleTestCase.cpp", 44 ) ) );
( CppUnit::assertEquals( (12), (13), CppUnit::SourceLine( "ExampleTestCase.cpp", 45 ), "" ) );
( CppUnit::assertDoubleEquals( (12.0), (11.99), (0.5), CppUnit::SourceLine( "ExampleTestCase.cpp", 46 ), "" ) );
}
3、源码解读
使用CPPUNIT进行单元测试的程序一般包含三个过程(从main函数可以看出来):1)测试对象的构建;2)运行测试对象;3)测试结果信息收集与显示(在每一个代码片段后面都会给出函数调用序列)。
1)测试对象(即上述用户自定义的测试类ExampelTestCase类及其测试方法)的构建过程。
这个过程主要是分a)和b)两方面,其中a)是TestSuiteFactory的登记,b)是所有Test对象的构造。
a) ExampleTestCase.cpp中有一行全局代码,先于main函数执行(如果有多个测试类,那么每个测试类的实现代码都应该包含这样的一行全局代码)
//在AutoRegisterSuite实例对象的构造函数中完成了TestSuite的登记操作,实例名字由宏动
//态创建,保证名字是唯一的。
static CppUnit::AutoRegisterSuite< ExampleTestCase > autoRegisterRegistry__4;
AutoRegisterSuite<ExampleTestCase>::AutoRegisterSuite--->
AutoRegisterSuite
template<class TestCaseType>
class AutoRegisterSuite
{
public:
//在构造函数里先取得一个TestFactoryRegistry,这个也是全局的命名为"All Tests"的
//Registry
AutoRegisterSuite() : m_registry( &TestFactoryRegistry::getRegistry())
{
//在构造函数里面自动注册一个TestSuiteFactory(复制构造一个对象)到全局的
//registry
m_registry->registerFactory( &m_factory );
}
AutoRegisterSuite( const std::string &name )
: m_registry( &TestFactoryRegistry::getRegistry( name ) )
{
m_registry->registerFactory( &m_factory );
}
~AutoRegisterSuite()
{
if ( TestFactoryRegistry::isValid() )
m_registry->unregisterFactory( &m_factory );
}
private:
TestFactoryRegistry *m_registry;
//TestSuiteFactory,实际加入的TestFactory类型是TestSuiteFactory
//<ExampleTestCase>,后面会用到
TestSuiteFactory<TestCaseType> m_factory;
};
AutoRegisterSuite<ExampleTestCase>::AutoRegisterSuite--->TestFactoryRegistry::getRegistry(此步见b部分)--->TestFactoryRegistry::registerFactory--->
void TestFactoryRegistry::registerFactory( TestFactory *factory )
{
//m_factories是一个set类型Factories,存放登记的TestFactory对象
m_factories.insert( factory );
}
AutoRegisterSuite<ExampleTestCase>::AutoRegisterSuite--->TestFactoryRegistry::getRegistry(此步见b部分)--->TestFactoryRegistry::registerFactory--->Factories::insert
b) 从main函数中开始
int main( int argc, char* argv[] )
{
//测试结果的记录采用设计模式中观察者模式TestResult是Subject
//TestResultCollector和下面的BriefTestProcessListener都是Observer
CPPUNIT_NS::TestResult controller;
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );
CPPUNIT_NS::TestRunner runner;
//这里是关键,构造了所有测试对象
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
runner.run( controller );
//输出测试结果
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
outputter.write();
return result.wasSuccessful() ? 0 : 1;
}
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->
TestFactoryRegistry
//name有默认值"All Tests",这个也是全局的Registry对象
TestFactoryRegistry& TestFactoryRegistry::getRegistry( const std::string &name )
{
return *TestFactoryRegistryList::getRegistry( name );
}
//构造一个新的TestSuite对象,把该TestFactoryRegistry下面的TestFactory全部
//拿出来makeTest一把,再把那些Test加入其中,返回TestSuite对象
Test* TestFactoryRegistry::makeTest()
{
TestSuite *suite = new TestSuite( m_name );
addTestToSuite( suite );
return suite;
}
void TestFactoryRegistry::addTestToSuite( TestSuite *suite )
{
for( Factories::iterator it = m_factories.begin();
it != m_factories.end();
++it )
{
TestFactory *factory = *it;
suite->addTest( factory->makeTest() );
}
}
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->
TestFactoryRegistryList
class TestFactoryRegistryList
{
private:
typedef CppUnitMap<std::string, TestFactoryRegistry *, std::less<std::string> > Registries;
Registries m_registries;
enum State {
doNotChange =0,
notCreated,
exist,
destroyed
};
static State stateFlag( State newState = doNotChange )
{
static State state = notCreated;
if ( newState != doNotChange )
state = newState;
return state;
}
static TestFactoryRegistryList *getInstance()
{
//TestFactoryRegistryList实质上是一个单体实例对象
static TestFactoryRegistryList list;
return &list;
}
TestFactoryRegistry *getInternalRegistry( const std::string &name )
{
Registries::const_iterator foundIt = m_registries.find( name );
if ( foundIt == m_registries.end() )
{
TestFactoryRegistry *factory = new TestFactoryRegistry( name );
m_registries.insert( std::pair<const std::string, TestFactoryRegistry*>( name, factory ) );
return factory;
}
return (*foundIt).second;
}
public:
TestFactoryRegistryList()
{
stateFlag( exist );
}
~TestFactoryRegistryList()
{
for ( Registries::iterator it = m_registries.begin(); it != m_registries.end(); ++it )
delete (*it).second;
stateFlag( destroyed );
}
static TestFactoryRegistry *getRegistry( const std::string &name )
{
// If the following assertion failed, then TestFactoryRegistry::getRegistry()
// was called during static variable destruction without checking the registry
// validity beforehand using TestFactoryRegistry::isValid() beforehand.
assert( isValid() );
if ( !isValid() ) // release mode
return NULL; // => force CRASH
return getInstance()->getInternalRegistry( name );
}
static bool isValid()
{
return stateFlag() != destroyed;
}
};
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
TestSuiteFactory
template<class TestCaseType>
class TestSuiteFactory : public TestFactory
{
public:
virtual Test *makeTest()
{
//这里正式调用前面由宏构造的ExampleTestCase的suite方法,注意这个suite()方
//法是一个静态方法!
return TestCaseType::suite();
}
};
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
--->TestSuiteFactory<ExampleTestCase>::makeTest(参考a部分)--->ExampleTestCase::suite
class ExampleTestCase : public CppUnit::TestFixture
{
public:
typedef ExampleTestCase TestFixtureType;
private:
//主要是返回用户写的TestFixture类的名字
static const CppUnit::TestNamer &getTestNamer__()
{
static CppUnit::TestNamer testNamer( typeid(ExampleTestCase) );
return testNamer;
}
public:
typedef CppUnit::TestSuiteBuilderContext<TestFixtureType> TestSuiteBuilderContextType;
static void addTestsToSuite( CppUnit::TestSuiteBuilderContextBase &baseContext )
{
//把每个测试方法用TestCaller使用具体的TestFixture类型封装起来,加入
//TestSuite中
TestSuiteBuilderContextType context( baseContext );
//生成TestCaller封装测试代码,加入TestSuite
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "example"), &TestFixtureType::examp
le, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "anotherExample"), &TestFixtureType
::anotherExample, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testAdd"), &TestFixtureType::testA
dd, context.makeFixture() ) ) );
context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testEquals"), &TestFixtureType::te
stEquals, context.makeFixture() ) ) );
}
static CppUnit::TestSuite *suite()
{
const CppUnit::TestNamer &namer = getTestNamer__();
std::auto_ptr<CppUnit::TestSuite> suite( new CppUnit::TestSuite( namer.getFixtureName() ));
CppUnit::ConcretTestFixtureFactory<TestFixtureType> factory;
CppUnit::TestSuiteBuilderContextBase context( *suite.get(), namer, factory );
TestFixtureType::addTestsToSuite( context );
return suite.release();
}
private:
typedef int CppUnitDummyTypedefForSemiColonEnding__;
......
}
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
--->TestSuiteFactory<ExampleTestCase>::makeTest(参考a部分)--->ExampleTestCase::suite--->ExampleTestCase::addTestsToSuite--->TestSuiteBuilderContext<ExampleTestCase>::addTest
TestCaller
template <class Fixture>
class TestCaller : public TestCase
{
//此处的类函数指针约定了用户自己编写的测试函数都要满足返回值为void
//且无参数列表(包括setUp和tearDown方法)
typedef void (Fixture::*TestMethod)();
public:
TestCaller( std::string name, TestMethod test ) :
TestCase( name ),
m_ownFixture( true ),
m_fixture( new Fixture() ),
m_test( test )
{
}
TestCaller(std::string name, TestMethod test, Fixture& fixture) :
TestCase( name ),
m_ownFixture( false ),
m_fixture( &fixture ),
m_test( test )
{
}
TestCaller(std::string name, TestMethod test, Fixture* fixture) :
TestCase( name ),
m_ownFixture( true ),
m_fixture( fixture ),
m_test( test )
{
}
~TestCaller()
{
if (m_ownFixture)
delete m_fixture;
}
void runTest()
{
//调用测试函数,比如ExampleTestCase中的example方法
(m_fixture->*m_test)();
}
void setUp()
{
//每次调用runTest之前都会调用setUp
m_fixture->setUp ();
}
void tearDown()
{
//每次调用runTest之前都会调用tearDown
m_fixture->tearDown ();
}
.......
};
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
--->TestSuiteFactory<ExampleTestCase>::makeTest(参考a部分)--->ExampleTestCase::suite--->ExampleTestCase::addTestsToSuite--->TestSuiteBuilderContext<ExampleTestCase>::addTest--->TestCaller<ExampleTestCase>::TestCaller
TestSuiteBuilderContextBase
void TestSuiteBuilderContextBase::addTest( Test *test )
{
//把TestCaller加入TestSuite
m_suite.addTest( test );
}
TestFixture *TestSuiteBuilderContextBase::makeTestFixture() const
{
//用TestFixtureFactory创建一个TestFixture对象
return m_factory.makeFixture();
}
TestSuiteBuilderContext
template<class Fixture>
class TestSuiteBuilderContext : public TestSuiteBuilderContextBase
{
public:
typedef Fixture FixtureType;
TestSuiteBuilderContext( TestSuiteBuilderContextBase &contextBase )
: TestSuiteBuilderContextBase( contextBase )
{
}
FixtureType *makeFixture() const
{
//无非是做了一下类型转换而已,生成一个具体类对象,为函数调用做准备
return CPPUNIT_STATIC_CAST( FixtureType *,
TestSuiteBuilderContextBase::makeTestFixture() );
}
};
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
--->TestSuiteFactory<ExampleTestCase>::makeTest(参考a部分)--->ExampleTestCase::suite--->ExampleTestCase::addTestsToSuite--->TestSuiteBuilderContext<ExampleTestCase>::addTest--->TestCaller<ExampleTestCase>::TestCaller--->TestSuiteBuilderContext<ExampleTestCase>::makeFixture--->TestSuiteBuilderContextBase::makeTestFixture--->TestSuite::addTest
class CPPUNIT_API TestRunner
{
public:
TestRunner( );
virtual ~TestRunner();
virtual void addTest( Test *test );
virtual void run( TestResult &controller, const std::string &testPath = "" );
protected:
//WrappingSuite继承自TestSuite,为后面遍历执行测试对象奠定了基础
class CPPUNIT_API WrappingSuite : public TestSuite
{
public:
WrappingSuite( const std::string &name = "All Tests" );
int getChildTestCount() const;
std::string getName() const;
void run( TestResult *result );
protected:
Test *doGetChildTestAt( int index ) const;
bool hasOnlyOneTest() const;
Test *getUniqueChildTest() const;
};
protected:
//TestSuiteFactory创建的TestSuite对象都会加入这个m_suite存储
WrappingSuite *m_suite;
private:
// Prevents the use of the copy constructor.
TestRunner( const TestRunner © );
// Prevents the use of the copy operator.
void operator =( const TestRunner © );
private:
};
void TestRunner::addTest( Test *test )
{
m_suite->addTest( test );
}
TestRunner::addTest--->TestFactoryRegistry::getRegistry--->TestFactoryRegistryList::getRegistry--->TestFactoryRegistryList::getInstance--->
TestFactoryRegistryList::getInternalRegistry--->TestFactoryRegistry::makeTest--->TestFactoryRegistry::addTestToSuite--->TestFactory::makeTest
--->TestSuiteFactory<ExampleTestCase>::makeTest(参考a部分)--->ExampleTestCase::suite--->ExampleTestCase::addTestsToSuite--->TestSuiteBuilderContext<ExampleTestCase>::addTest--->TestCaller<ExampleTestCase>::TestCaller--->TestSuiteBuilderContext<ExampleTestCase>::makeFixture--->TestSuiteBuilderContextBase::makeTestFixture--->TestSuite::addTest--->WrappingSuite::addTest--->TestSuite::addTest
至此创建过程已经完成了!
2)执行测试对象
由main函数TestRunner对象启动运行所有测试对象
int main( int argc, char* argv[] )
{
//Create the event manager and test controller
CPPUNIT_NS::TestResult controller;
// Add a listener that colllects test result
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );
// Add a listener that print dots as test run.
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );
// Add the top suite to the test runner
CPPUNIT_NS::TestRunner runner;
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
//所有测试对象都是用数组递归存储的,runner遍历这些测试对象族调用run方法,完成
//所有测试对象的测试,并由controller记录执行信息
runner.run( controller );
// Print test in a compiler compatible format.
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
outputter.write();
return result.wasSuccessful() ? 0 : 1;
}
void TestRunner::run( TestResult &controller, const std::string &testPath )
{
//取出最顶端的Test对象并交给controller执行
TestPath path = m_suite->resolveTestPath( testPath );
Test *testToRun = path.getChildTest();
//最顶端的Test对象是TestSuite对象!
controller.runTest( testToRun );
}
TestRunner::run--->TestResult::runTest
void TestResult::runTest( Test *test )
{
startTestRun( test );
test->run( this );
endTestRun( test );
}
void TestResult::startTestRun( Test *test )
{
//TestResult是线程安全的,因此很多地方都会加锁
ExclusiveZone zone( m_syncObject );
//通知所有登记的TestListener对象要startTestRun了
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->startTestRun( test, this );
}
void TestResult::endTestRun( Test *test )
{
ExclusiveZone zone( m_syncObject );
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->endTestRun( test, this );
}
备注:startTestRun/endTestRun在整个程序运行中只会执行一次,与下面
的startTest/endTest要区分开,后者是每个测试方法之前都会执行一次
TestRunner::run--->TestResult::runTest--->TestResult::startTestRun--->TestListener::startTestRun--->Test::run--->TestSuite::run--->TestComposite::run
备注:由于最顶端的Test对象是TestSuite对象,所以其run方法实质调用的是TestComposite的run方法(TestSuite是TestComposite的子类,且TestSuite没有自己的run方法)
void TestComposite::run( TestResult *result )
{
doStartSuite( result );
doRunChildTests( result );
doEndSuite( result );
}
void TestComposite::doStartSuite( TestResult *controller )
{
controller->startSuite( this );
}
void TestComposite::doRunChildTests( TestResult *controller )
{
int childCount = getChildTestCount();
for ( int index =0; index < childCount; ++index )
{
if ( controller->shouldStop() )
break;
//此处执行TestSuite里面的各个Test对象,从构造过程看可以知道实际上是
//TestCaller对象,而TestCaller没有自己的run方法,其run方法继承自TestCase
getChildTestAt( index )->run( controller );
}
}
void TestComposite::doEndSuite( TestResult *controller )
{
controller->endSuite( this );
}
TestRunner::run--->TestResult::runTest--->TestResult::startTestRun--->TestListener::startTestRun--->Test::run--->TestSuite::run--->TestComposite::run--->TestComposite::doStartSuite--->TestResult::startSuite
//startSuite/endSuite的执行类似于startTestRun/endTestRun
void TestResult::startSuite( Test *test )
{
ExclusiveZone zone( m_syncObject );
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->startSuite( test );
}
void TestResult::endSuite( Test *test )
{
ExclusiveZone zone( m_syncObject );
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->endSuite( test );
}
TestRunner::run--->TestResult::runTest--->TestResult::startTestRun--->循环[TestListener::startTestRun]--->Test::run--->TestSuite::run--->TestComposite::run--->TestComposite::doStartSuite--->TestResult::startSuite--->循环[TestListener::startSuite]--->TestComposite::doRunChildTests--->循环[Test::run--->TestCaller::run--->TestCase::run]
void TestCase::run( TestResult *result )
{
//result->protect中会捕捉异常,并记录信息
//TestCaseMethodFunctor是一个函数算子,封装了Test对象和函数指针
result->startTest(this);
//对于每个测试方法,都先执行其所属TestFixture的setUp方法
if ( result->protect( TestCaseMethodFunctor( this, &TestCase::setUp ),
this,
"setUp() failed" ) )
{
//执行测试方法,TestCase::runTest执行真正的测试函数,参见测试对象构造部分
result->protect( TestCaseMethodFunctor( this, &TestCase::runTest ),
this );
}
//对于每个测试方法,都后执行其所属TestFixture的tearDown方法
result->protect( TestCaseMethodFunctor( this, &TestCase::tearDown ),
this,
"tearDown() failed" );
result->endTest( this );
}
备注:关于TestResult::protect方法的讨论放到下节详细分析。
//startTest/endTest的执行类似于startTestRun/endTestRun
void TestResult::startTest( Test *test )
{
ExclusiveZone zone( m_syncObject );
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->startTest( test );
}
void TestResult::endTest( Test *test )
{
ExclusiveZone zone( m_syncObject );
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->endTest( test );
}
TestRunner::run--->TestResult::runTest--->TestResult::startTestRun--->循环[TestListener::startTestRun]--->Test::run--->TestSuite::run--->TestComposite::run--->TestComposite::doStartSuite--->TestResult::startSuite--->循环[TestListener::startSuite]--->TestComposite::doRunChildTests--->循环[Test::run--->TestCaller::run--->TestCase::run-->TestResult::startTest--->循环[TestListener::startTest]--->TestCaller::setUp--->ExampleTestCase::setUp--->TestCaller::runTest--->ExampleTestCase::example(这里假定执行的example方法,其他的类似)--->TestCaller::tearDown--->ExampleTestCase::tearDown--->TestResult::endTest--->循环[TestListener::endTest]]--->TestComposite::doEndSuite--->TestResult::endSuite--->循环[TestListener::endSuite]--->TestResult::endTestRun--->循环[TestListener::endTestRun]
3)记录与显示执行结果
a)首先看看最常用也是最通用的断言方式是用CPPUNIT_ASSERT宏(其它宏类似),比如
CPPUNIT_ASSERT( result == 6.0 );
展开后如下所示:
( CppUnit::Asserter::failIf( !(result == 6.0), CppUnit::Message( "assertion failed", "Expression: " "result == 6.0"), CppUnit::SourceLine( "ExampleTestCase.cpp", 28 )));
Asserter
void Asserter::fail( std::string message, const SourceLine &sourceLine )
{
fail( Message( "assertion failed", message ), sourceLine );
}
void Asserter::fail( const Message &message, const SourceLine &sourceLine )
{
throw Exception( message, sourceLine );
}
void Asserter::failIf( bool shouldFail, const Message &message, const SourceLine &sourceLine )
{
if ( shouldFail )
fail( message, sourceLine );
}
void Asserter::failIf( bool shouldFail, std::string message, const SourceLine &sourceLine )
{
failIf( shouldFail, Message( "assertion failed", message ), sourceLine );
}
主要是Asserter、Message、SourceLine、Exception类构成,这些类的结构非常简单,而且关系也简单,看看代码就一目了然。其中Asserter封装了一些异常抛出方法,Message则是包含了断言表达式信息,sourceLine则包含了断言所在代码行的信息,Exception则包含了Message和sourceLine,当断言失败的时候抛出异常,那么测试将会失败,否则测试成功。
b)下面再来看看包裹住用户编写的测试函数的TestResult::protect方法,其实protect的设计非常巧妙,运用了设计模式中命令模式、装饰者和责任链三个设计模式。
class CPPUNIT_API TestResult : protected SynchronizedObject
{
......
protected:
typedef CppUnitDeque<TestListener *> TestListeners;
TestListeners m_listeners;
//在TestResult里面实质上是包含了一个ProtectorChain(protector组成的链表),也就
//是说每个用户编写的测试函数将会经过这个链表的所有protector,这个入口是
//ProtectorChain(ProtectorChain也是继承自Protector)的protector方法,见下面分析
ProtectorChain *m_protectorChain;
bool m_stop;
......
}
TestResult::TestResult( SynchronizationObject *syncObject )
: SynchronizedObject( syncObject )
, m_protectorChain( new ProtectorChain() )
, m_stop( false )
{
//实际上是有一个默认的protector在TestResult构造函数中加入这个链表中
//因此,实质执行的时候只有一个protector,但是cppunit这种设计可以很方便
//进行扩展,编写更多的protector
m_protectorChain->push( new DefaultProtector() );
}
bool TestResult::protect( const Functor &functor,
Test *test,
const std::string &shortDescription )
{
//ProtectorContext只是封装了Test、TestResult和描述信息的对象而已
ProtectorContext context( test, this, shortDescription );
//这个是用户编写的测试函数执行入口,由ProtectorChain去调用里面各个
//Protector
return m_protectorChain->protect( functor, context );
}
class CPPUNIT_API Protector
{
public:
virtual ~Protector();
virtual bool protect( const Functor &functor,
const ProtectorContext &context ) =0;
protected:
void reportError( const ProtectorContext &context,
const Exception &error ) const;
void reportError( const ProtectorContext &context,
const Message &message,
const SourceLine &sourceLine = SourceLine() ) const;
void reportFailure( const ProtectorContext &context,
const Exception &failure ) const;
Message actualMessage( const Message &message,
const ProtectorContext &context ) const;
};
class CPPUNIT_API Functor
{
public:
virtual ~Functor();
virtual bool operator()() const =0;
};
class ProtectorChain::ProtectFunctor : public Functor
{
public:
ProtectFunctor( Protector *protector,
const Functor &functor,
const ProtectorContext &context )
: m_protector( protector )
, m_functor( functor )
, m_context( context )
{
}
bool operator()() const
{
return m_protector->protect( m_functor, m_context );
}
private:
Protector *m_protector;
const Functor &m_functor;
const ProtectorContext &m_context;
};
//ProtectorChain也是一个Protector,这种设计非常灵活
class CPPUNIT_API ProtectorChain : public Protector
{
public:
~ProtectorChain();
void push( Protector *protector );
void pop();
int count() const;
bool protect( const Functor &functor,const ProtectorContext &context );
private:
//此类又是一个函数算子,同样使用了装饰者模式,里面进一步把用户编写的测试函数
//以及和Protector、ProtectorContext给封装起来,这里为后面的Protector递归包裹
//奠定了基础
class ProtectFunctor;
private:
typedef CppUnitDeque<Protector *> Protectors;
//所有Protector都在这里登记在这里
Protectors m_protectors;
typedef CppUnitDeque<Functor *> Functors;
};
下面这个方法是protector设计中的核心函数
bool ProtectorChain::protect( const Functor &functor,
const ProtectorContext &context )
{
if ( m_protectors.empty() )
return functor();
//这里把登记的所有Protector进行递归嵌套,注意Protector的嵌套顺序
//跟Protector的登记顺序是相反的,也就是先登记的Protector会先执行
Functors functors;
for ( int index = m_protectors.size()-1; index >= 0; --index )
{
const Functor &protectedFunctor =
functors.empty() ? functor : *functors.back();
functors.push_back( new ProtectFunctor( m_protectors[index],
protectedFunctor,
context ) );
}
//取出最顶层的一个ProtectorFunctor进行最终的函数执行
const Functor &outermostFunctor = *functors.back();
bool succeed = outermostFunctor();
for ( unsigned int deletingIndex = 0; deletingIndex < m_protectors.size(); ++deletingIndex )
delete functors[deletingIndex];
return succeed;
}
最后看看CPPUNIT实际上只是使用了一个Protector,即为DefaultProtector
bool DefaultProtector::protect( const Functor &functor,
const ProtectorContext &context )
{
try
{
return functor();
}
catch ( Exception &failure )
{
//CPPUINT预期的异常,一般为用户编写的测试函数中的断言错误
reportFailure( context, failure );
}
catch ( std::exception &e )
{
std::string shortDescription( "uncaught exception of type " );
#if CPPUNIT_USE_TYPEINFO_NAME
shortDescription += TypeInfoHelper::getClassName( typeid(e) );
#else
shortDescription += "std::exception (or derived).";
#endif
Message message( shortDescription, e.what() );
//CPPUINT非预期的异常,是C++抛出的异常
reportError( context, message );
}
catch ( ... )
{
//CPPUINT非预期的异常,是C++抛出的异常
reportError( context, Message( "uncaught exception of unknown type") );
}
return false;
}
备注:上面的reportError和reportFailure是错误信息的传递的第一站,下一节将详细分析
这里总结一下TestResult::protect之后的函数调用序列:
TestResult::protect--->ProtectorChain::protect--->[DefaultProtector::protect--->用户编写的测试函数]
实际上最后一步正如上面ProtectorChain::protect所分析的,是一个递归嵌套,然后递归展开的执行过程,而且是支持多个Protector的(虽然实际上CPPUINT只用了一个Protector)
c)最后看看错误信息是如何传递与记录的。
上面的DefaultProtector中protect函数里面,通过reportError和reportFailure把信息传递出去。这两个函数继承自Protector类。
void Protector::reportError( const ProtectorContext &context,
const Exception &error ) const
{
std::auto_ptr<Exception> actualError( error.clone() );
actualError->setMessage( actualMessage( actualError->message(), context ) );
//调用TestResult的addError方法
context.m_result->addError( context.m_test, actualError.release() );
}
void Protector::reportFailure( const ProtectorContext &context,
const Exception &failure ) const
{
std::auto_ptr<Exception> actualFailure( failure.clone() );
actualFailure->setMessage( actualMessage( actualFailure->message(), context ) );
//调用TestResult的addFailure方法
context.m_result->addFailure( context.m_test, actualFailure.release() );
}
前面说过,测试结果的记录采用设计模式中观察者模式,TestResult是Subject,所以自然这里的错误信息传递会通知所有在这个TestResult对象登记的所有Observer对象,这CPPUNIT中,TestResultCollector、BriefTestProgressListener(参考main函数开头几行)等扮演了Observer的角色。
void TestResult::addError( Test *test, Exception *e )
{
TestFailure failure( test, e, true );
addFailure( failure );
}
void TestResult::addFailure( Test *test, Exception *e )
{
TestFailure failure( test, e, false );
addFailure( failure );
}
void TestResult::addFailure( const TestFailure &failure )
{
ExclusiveZone zone( m_syncObject );
//通知所有Observer对象
for ( TestListeners::iterator it = m_listeners.begin();
it != m_listeners.end();
++it )
(*it)->addFailure( failure );
}
void TestResultCollector::addFailure( const TestFailure &failure )
{
TestSuccessListener::addFailure( failure );
ExclusiveZone zone( m_syncObject );
if ( failure.isError() )
++m_testErrors;
//用一个TestFailure队列装起来
m_failures.push_back( failure.clone() );
}
void BriefTestProgressListener::addFailure( const TestFailure &failure )
{
//直接打印到标准输出,及时提示测试者
stdCOut() << " : " << (failure.isError() ? "error" : "assertion");
m_lastTestFailed = true;
}
最终测试信息的输出是由Outputter类族进行的,在这个例子中是CompilerOutputter类,由于比较简单,这里省略。
四、后记
其实CPPUNIT能够做的事情很有限,但是对于管理测试代码以及执行测试代码的自动化方面具有优势,当然CPPUNIT本身也是可以扩展的,只要对它的代码进行改动并重新make就可以了。
对于代码编写,越简短的函数一般也是越简单,这样测试起来就相对容易,不会那么容易漏掉一些测试用例。对于核心和重要的代码,是应该进行白盒测试的,在使用CPPUNIT测试的过程中也许会漏掉或者有些情况是在功能测试下几乎没机会测试得到的,当然如果在测试过程中发现失败的也可以迅速进行定位,在回归测试中也很方便,重新make一下测试程序再执行一遍就行了。