您的位置:首页 > 编程语言 > C语言/C++

学习笔记:解读CppUnit源码8

2009-11-09 08:39 906 查看
首先还是从如何测试开始剖析代码把,以下就是ExampleTestCase.h的代码
class ExampleTestCase : public CPPUNIT_NS::TestFixture
{
CPPUNIT_TEST_SUITE( ExampleTestCase );
CPPUNIT_TEST( example );
CPPUNIT_TEST( anotherExample );
CPPUNIT_TEST( testAdd );
CPPUNIT_TEST( testEquals );
CPPUNIT_TEST_SUITE_END();

protected:
double m_value1;
double m_value2;

public:
void setUp();

protected:
void example();
void anotherExample();
void testAdd();
void testEquals();
};


画面第一行:ExampleTestCase就是我们为一个类生成的测试类,他必须要继承自测试框架类CPPUNIT_NS::TestFixture这样才可以正常测试。

画面第三~八行:其中,宏CPPUNIT_TEST_SUITE(), 宏CPPUNIT_TEST()和宏CPPUNIT_TEST_SUITE_END()被设计用来简化创建测试包的过程。当然要构造一个正常的测试还有一个前提,就是在ExampleTestCase.h这个文件中加入#include <cppunit/extensions/HelperMacros.h>,因为这几个简化映射用的宏都是存在于HelperMacros.h中

先介绍第一个宏CPPUNIT_TEST_SUITE( ExampleTestCase );

//开始创建一个TestSuite
//把测试用例比如ExampleTestCase重新声明为TestFixtureType类型
//声明一个静态的私有函数getTestNamer__,得到测试用例的名字
//addTestsToSuite:加入测试用例到测试组
#define CPPUNIT_TEST_SUITE( ATestFixtureType )                              /
public:                                                                   /
typedef ATestFixtureType TestFixtureType;                               /
/
private:                                                                  /
static const CPPUNIT_NS::TestNamer &getTestNamer__()                    /
{                                                                       /
static CPPUNIT_TESTNAMER_DECL( testNamer, ATestFixtureType );         /
return testNamer;                                                     /
}                                                                       /
/
public:                                                                   /
typedef CPPUNIT_NS::TestSuiteBuilderContext<TestFixtureType>            /
TestSuiteBuilderContextType;                                /
/
static void                                                             /
addTestsToSuite( CPPUNIT_NS::TestSuiteBuilderContextBase &baseContext ) /
{                                                                       /
TestSuiteBuilderContextType context( baseContext )


画面第七行:测试构造类被重新定义为TestFixtureType名字方便下面使用。

画面第十行:getTestNamer__是类的静态方法,主要是利用C++的RTTI信息取得用户写的TestFixture类的类名字,getTestNamer__的返回值是一个测试类的对象TestNamer。TestNamer这个类定义如下,比较的简单,这里就不作说明了

class CPPUNIT_API MyTestNamer
{
public:
MyTestNamer( const std::string &fixtureName )
: m_fixtureName( fixtureName )
{
};

virtual ~MyTestNamer(){};

virtual std::string getFixtureName() const
{
return m_fixtureName;
};

virtual std::string getTestNameFor( const std::string &testMethodName ) const
{
return  getFixtureName() + "::" + testMethodName;
};

protected:
std::string m_fixtureName;
};


请大家注意宏:CPPUNIT_TESTNAMER_DECL,所以说这个测试类的名字就和这两个参数当字符串相连接,以下是CPPUNIT_TESTNAMER_DECL

#if CPPUNIT_USE_TYPEINFO_NAME
#  define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType )       /
CPPUNIT_NS::TestNamer variableName( typeid(FixtureType) )
#else
#  define CPPUNIT_TESTNAMER_DECL( variableName, FixtureType )       /
CPPUNIT_NS::TestNamer variableName( std::string(#FixtureType) )
#endif


让我们再次把目光回到CPPUNIT_TEST_SUITE的代码上。

代码第17-18行:把TestSuiteBuilderContext类型重新声明为TestSuiteBuilderContextType,又是变个名称而已,但是TestSuiteBuilderContext又是什么东西呢?在这里用到了一个设计模式Builder,稍后我们再来看它的代码。

代码第21行:声明了一个静态的公有函数addTestsToSuite,加入测试用例到测试组,参数是一个TestSuiteBuilderContextBase,这个就是Builder设计模式中所有ConcreteBuilder的基类了。接下来我们看看如何添加测试用例进去。看CPPUNIT_TESTCPPUNIT_TEST_SUITE_ADD_TEST宏的定义:

//加一个测试用例
#define CPPUNIT_TEST_SUITE_ADD_TEST( test ) /
context.addTest( test )

//该宏用于每次向测试包添加一个测试方法,包括函数名称和函数指针作为参数
#define CPPUNIT_TEST( testMethod )                        /
CPPUNIT_TEST_SUITE_ADD_TEST(                           /
( new CPPUNIT_NS::MyTestCaller<TestFixtureType>(    /
context.getTestNameFor( #testMethod),   /
&TestFixtureType::testMethod,           /
context.makeFixture() ) ) )


这里new了一个TestCaller,TestCaller继承自testcase类,具体的我们先放放。"#testMethod"就很明显了,传入了测试参数的名称字符串。&TestFixtureType::testMethod是一个指向成员函数的指针。context.makeFixture() 是用来创建测试装置的。

接下来让我们看看这组宏的结束CPPUNIT_TEST_SUITE_END:

#define CPPUNIT_TEST_SUITE_END()                                               /
}                                                                          /
/
static CPPUNIT_NS::MyTestSuite *suite()                                      /
{                                                                          /
const CPPUNIT_NS::MyTestNamer &namer = getTestNamer__();                   /
std::auto_ptr<CPPUNIT_NS::MyTestSuite> suite(                              /
new CPPUNIT_NS::MyTestSuite( namer.getFixtureName() ));             /
CPPUNIT_NS::MyConcretTestFixtureFactory<TestFixtureType> factory;          /
CPPUNIT_NS::MyTestSuiteBuilderContextBase context( *suite.get(),           /
namer,                  /
factory );              /
TestFixtureType::addTestsToSuite( context );                             /
return suite.release();                                                  /
}                                                                          /
private: /* dummy typedef so that the macro can still end with ';'*/         /
typedef int CppUnitDummyTypedefForSemiColonEnding__


代码第2行:addTestsToSuite函数结束的标志。

代码第4行:静态的suite函数,返回测试包对象的指针。

代码第6行:getTestNamer__方法是在同名的那个START宏里面定义的。

代码第9行:一个装置器的工厂。
代码第10-12行:Builder基类的初始化。

代码第13行:addTestsToSuite调用的地方,调用后就把测试用例加入到了TestSuiteBuilderContextBase::m_tests中。

代码第14行:suite.release放弃包装在智能指针里面的内容,把它释放出来给外面。如果不执行它,智能指针会自动对资源进行回收。

代码第17行:就是一定要让这个宏的后面加上分号才可以正常编译通过的一个方法。

看了上面的分析,知道了addTestsToSuite方法是注册测试用例到TestSuiteBuilderContextBase中的方法,那么测试用例不是用工厂来创建的阿,那么这些测试用例的工厂注册又在哪里呢?在CPPUNIT_TEST_SUITE_REGISTRATIONCPPUNIT_TEST_SUITE_NAMED_REGISTRATION2个宏里,细心的人一定看到了,在ExampleTestCase.cpp的最上面,使用了CPPUNIT_TEST_SUITE_REGISTRATION宏。

// 添加一个TestSuite到一个指定的TestFactoryRegistry工厂
#define CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName ) /
static CPPUNIT_NS::MyAutoRegisterSuite< ATestFixtureType >                   /
CPPUNIT_MAKE_UNIQUE_NAME(autoRegisterRegistry__ )(suiteName)

//将自动把测试包实例注册
#define CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType )      /
static CPPUNIT_NS::MyAutoRegisterSuite< ATestFixtureType >       /
CPPUNIT_MAKE_UNIQUE_NAME(autoRegisterRegistry__ )


到这里几个最主要的宏的介绍都全了。一行短短的宏,把这么多信息包含进了里面去。让我想到了mfc中宏的应用。最后是TestSuiteBuilderContextBase.h

class CPPUNIT_API MyTestSuiteBuilderContextBase
{
public:
/*
* 应该调用CPPUNIT_TEST_SUITE()来建造.
*/
MyTestSuiteBuilderContextBase( MyTestSuite &suite,
const MyTestNamer &namer,
MyTestFixtureFactory &factory )
: m_suite( suite )
, m_namer( namer )
, m_factory( factory )
{
};

virtual ~MyTestSuiteBuilderContextBase(){};
/*!增加测试用例,把TestCaller加入TestSuite
使用CPPUNIT_TEST_SUITE_ADD_TEST宏添加
*/
void addTest( MyTest *test )
{
m_suite.addTest(test);
};
/*!返回FixtureName
*/
std::string getFixtureName() const
{
return m_namer.getFixtureName();
};

/*!返回FixtureName + MethodName
*/
std::string getTestNameFor( const std::string &testMethodName ) const
{
return m_namer.getTestNameFor( testMethodName );
};

void addProperty( const std::string &key,
const std::string &value )
{
Propertys::iterator it = m_properties.begin();
for ( ; it != m_properties.end(); ++it )
{
if ( (*it).first == key )
{
(*it).second = value;
return;
}
}

Property property( key, value );
m_properties.push_back( property );

};

const std::string getStringProperty( const std::string &key ) const
{
Properties::const_iterator it = m_properties.begin();
for ( ; it != m_properties.end(); ++it )
{
if ( (*it).first == key )
return (*it).second;
}
return "";
};

protected:
//TestFixtureFactory创建一个TestFixture对象
MyTestFixture *makeTestFixture() const
{
return m_factory.makeFixture();
};

// Notes: we use a vector here instead of a map to work-around the
// shared std::map in dll bug in VC6.
// See http://www.dinkumware.com/vc_fixes.html for detail.
typedef std::pair<std::string,std::string> Property;
typedef CppUnitVector<Property> Properties;

MyTestSuite &m_suite;
const MyTestNamer &m_namer;
MyTestFixtureFactory &m_factory;

private:
//字符串的形式记录测试包创建过程中的所有属性
Properties m_properties;
};

template<class Fixture>
class TestSuiteBuilderContext : public MyTestSuiteBuilderContextBase
{
public:
typedef Fixture FixtureType;

TestSuiteBuilderContext( MyTestSuiteBuilderContextBase &contextBase )
: MyTestSuiteBuilderContextBase( contextBase )
{
}

FixtureType *makeFixture() const
{
return CPPUNIT_STATIC_CAST( FixtureType *,
MyTestSuiteBuilderContextBase::makeTestFixture() );
}
};


现在看来,这个类的功能就一目了然了把,TestSuiteBuilderContextBase聚集了装置器工厂和一组测试用例。一组测试用例(一组用例的名字是主测类名)在CPPUNIT_TEST_SUITE_REGISTRATION中都进行了注册。在添加测试用例TestCaller的时候,需要用到装置器工厂。

TestCaller.h

//一个TestCaller仅对应一个Test类
//根据一个fixture创建一个测试用例,单独测试的时候用到
template <class Fixture>
class MyTestCaller : public MyTestCase
{
typedef void (Fixture::*TestMethod)();
public:
// 由TestCaller创建fixture,负责维护其生命周期
MyTestCaller( std::string name, TestMethod test )
:MyTestCase( name ),
m_ownFixture( true ),
m_fixture( new Fixture() ),
m_test( test )
{
};

// 由外界传入fixture,TestCaller不负责维护其生命周期
MyTestCaller(std::string name, TestMethod test, Fixture& fixture)
:MyTestCase( name ),
m_ownFixture( false ),
m_fixture( &fixture ),
m_test( test )
{
};

// 由外界传入fixture,由TestCaller负责维护其生命周期
MyTestCaller(std::string name, TestMethod test, Fixture* fixture) :
MyTestCase( name ),
m_ownFixture( true ),
m_fixture( fixture ),
m_test( test )
{
}

//判断是否销毁m_fixture
~MyTestCaller()
{
if (m_ownFixture)
delete m_fixture;
}

void runTest()
{
(m_fixture->*m_test)();
}

void setUp()
{
m_fixture->setUp ();
}

void tearDown()
{
m_fixture->tearDown ();
}

std::string toString() const
{
return "TestCaller " + getName();
}

private:
MyTestCaller( const MyTestCaller &other );
MyTestCaller &operator =( const MyTestCaller &other );

private:
bool m_ownFixture;// 若为true,则由TestCaller负责维护m_fixture的生命周期
Fixture *m_fixture;//指向TestFixture实例的指针
TestMethod m_test;// 指向某个test方法的函数指针
};


这个类的模板参数Fixture就是装置器,为了更方便宏的使用才用了模板参数。看代码可以知道,这里用了策略模式。一个类的所有测试用例都用了同一个装置器。而具体的运行哪个策略,可以根据成员函数的指针指向的函数来确定。概括的说,TestCaller其实是一个辅助类,负责把ExampleTestCase和TestCase关联起来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: