前言
我们要分析的源码样本就是simple工程,因为这个工程顾名思义,肯定是最简单的嘛。这个工程里的源码主要包括两个部分:
- main函数
- ExampleTestCase类
main函数比较复杂,我们先跳过这部分,先看ExampleTestCase类。
ExampleTestCase类
这个类派生自TestFixture,上一章我们已经说过了,这种方式自定义测试类比较简单。先看看基类的声明:
{
public :
virtual ~ TestFixture() {};
// ! /brief Set up context before running a test.
virtual void setUp() {};
// ! Clean up after the test run.
virtual void tearDown() {};
};
比较简单,声明了两个虚函数setUp和tearDown,默认实现是空。上一章我们已经说过了,这两个函数负责测试用例一些公共资源的初始化和清理。
下面我们看看ExampleTestCase类的声明:
{
CPPUNIT_TEST_SUITE( ExampleTestCase );
CPPUNIT_TEST( example );
CPPUNIT_TEST( anotherExample );
CPPUNIT_TEST( testAdd );
CPPUNIT_TEST( testDivideByZero );
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 testDivideByZero();
void testEquals();
};
我们看到,这个类还是比较简单的,其中的setUp函数重载了,查一下实现:
{
m_value1 = 2.0 ;
m_value2 = 3.0 ;
}
仅仅是初始化两个成员变量。基类的另一个虚函数tearDown没有重载,还是空。
再看看其它几个自定义的测试函数,都比较简单。
真正有问题的是开头的那几个宏,这几个宏负责构建我们的测试包,本章将详细的讲述这几个宏,揭示它们背后的秘密。
三个宏定义
CPPUNIT_TEST_SUITE
切换到CPPUNIT_TEST_SUITE的宏定义上,真是超长的宏定义啊:
2 public : /
3 typedef ATestFixtureType TestFixtureType; /
4 /
5 private : /
6 static const CPPUNIT_NS::TestNamer & getTestNamer__() /
7 { /
8 static CPPUNIT_TESTNAMER_DECL( testNamer, ATestFixtureType ); /
9 return testNamer; /
10 } /
11 /
12 public : /
13 typedef CPPUNIT_NS::TestSuiteBuilderContext < TestFixtureType > /
14 TestSuiteBuilderContextType; /
15 /
16 static void /
17 addTestsToSuite( CPPUNIT_NS::TestSuiteBuilderContextBase & baseContext ) /
18 { /
19 TestSuiteBuilderContextType context( baseContext )
不过看多了MFC消息映射的宏定义,这东西其实也没啥难度,或许,原本这东西就是从MFC那里学来的。
- 代码行3,首先把我们自己的测试用例ExampleTestCase重新声明为TestFixtureType类型,就是变个名称而已,使宏看起来好看一点
- 代码行6,然后,声明一个静态的私有函数getTestNamer__,得到测试用例的名字
- 代码行8,这个函数里面又是一个宏:
#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) )原来不过是声明一个变量而已,变量类型是TestNamer,构造函数的参数就是"ExampleTestCase"
- 代码行13-14,把TestSuiteBuilderContext类型重新声明为TestSuiteBuilderContextType,又是变个名称而已,但是TestSuiteBuilderContext又是什么东西?从名称上来看,很明显,这 里采用了Builder设计模式,先放放,回头我们再细看。
- 代码行16-17,再声明一个静态的公有函数addTestsToSuite,加入测试用例到测试组,参数是什么?就是一个TestSuiteBuilderContextBase,看起来这家伙 就是Builder设计模式中所有ConcreteBuilder的基类了
- 代码行19,声明了一个对象context,类型是一个ConcreteBuilder,参数就是baseContext,ConcreteBuilder的基类,注意,这个函数没有实现完嘛,因为没有结束的},很明显,这是为了需要后续的宏继续写下去
总结一下,这个宏用getTestNamer__函数注册了自定义测试类的名称作为标识,然后用addTestsToSuite函数开始注册自定义测试函数
CPPUNIT_TEST
切换到CPPUNIT_TEST的宏定义上:
2 CPPUNIT_TEST_SUITE_ADD_TEST( /
3 ( new CPPUNIT_NS::TestCaller < TestFixtureType > ( /
4 context.getTestNameFor( #testMethod), /
5 & TestFixtureType::testMethod, /
6 context.makeFixture() ) ) )
- 代码行2,先看CPPUNIT_TEST_SUITE_ADD_TEST的宏定义
#define CPPUNIT_TEST_SUITE_ADD_TEST( test ) /
context.addTest( test )还好,这句好懂,就是给上下文加一个测试用例嘛。
- 代码行3,就是new一个TestCaller而已 ,这个类我们上一章说过一点,这是一个辅助类,但是它到底是如何起作用的呢?待查
- 代码行4,又是一个名称相关的东西,一个‘#’号就暴露无疑了,这里传入了测试参数的名称字符串
- 代码行5,一个指向成员函数的指针,还好,没忘了这么偏的语法,不过想想也是,这里很明显是要注册每个自定义测试函数的信息嘛,传入一个函数名称用于标识,传入一个函数指针用于执行
- 代码行6,这里让context上下文创建测试装置
总结一下,这个宏用TestCaller类注册了一个测试函数的信息,包括函数名称和函数指针,然后再加入到context上下文 中。
CPPUNIT_TEST_SUITE_END
切换到CPPUNIT_TEST_SUITE_END的宏定义上:
2 } /
3 /
4 static CPPUNIT_NS::TestSuite * suite() /
5 { /
6 const CPPUNIT_NS::TestNamer & namer = getTestNamer__(); /
7 std::auto_ptr < CPPUNIT_NS::TestSuite > suite( /
8 new CPPUNIT_NS::TestSuite( namer.getFixtureName() )); /
9 CPPUNIT_NS::ConcretTestFixtureFactory < TestFixtureType > factory; /
10 CPPUNIT_NS::TestSuiteBuilderContextBase context( * suite. get (), /
11 namer, /
12 factory ); /
13 TestFixtureType::addTestsToSuite( context ); /
14 return suite.release(); /
15 } /
16 private : /* dummy typedef so that the macro can still end with ';' */ /
17 typedef int CppUnitDummyTypedefForSemiColonEnding__
我再晕,又是这么长的一个宏啊。
- 代码行2,终于看到addTestsToSuite函数结束的标志了
- 代码行4,一波未平一波又起啊,又搞出来一个suite函数,返回测试包对象的指针
- 代码行6,首先得到测试类的名字,这个函数我们刚才看过了
- 代码行7-8,然后用这个测试类的名字构造一个测试包的对象,这里用到了智能指针,当然是为了一旦有问题不用手动delete了
- 代码行9,一个ConcretTestFixtureFactory,一个测试类的工厂 ,注意这里是模板类,类型就是自定义测试类,居然是工厂设计模式,看样子应该是工厂方法设计模式吧,待查
- 代码行10-12,咦,前面看到过的Builder基类怎么又来凑热闹了,不过也别说,这家伙要干的事情也真是麻烦,参数这么多,他要求有测试包对象,测试包名称,测试类工厂
- 代码行13,addTestsToSuite,好熟悉的名字啊,原来就是第一个宏里面的函数嘛,原来这里就是它调用的地方啊
- 代码行14,release了,返回测试包的指针,智能指针清空了
- 代码行17,基本上这就是占位子用的,名字起得也很清楚,这是为了让编译器检查写宏的时候别忘了写那个‘;’,这个技巧倒是挺好玩的
总结一下,这个宏只有一个函数suite,用于创建测试包,这个函数不仅仅是addTestsToSuite函数的结束,同时也是addTestsToSuite调用的地方。
总结
宏CPPUNIT_TEST_SUITE,CPPUNIT_TEST,CPPUNIT_TEST_SUITE_END,这三个宏 彼此配合,形成了以下模式:
CPPUNIT_TEST( 成员函数 );
CPPUNIT_TEST( 成员函数 );
CPPUNIT_TEST( 成员函数 );
。。。
CPPUNIT_TEST_SUITE_END();
其中注册了自定义的测试类信息,注册了所有自定义的测试函数信息,最后通过一个suite函数创建了一个测试包的对象。
遗留问题
上一节,我们初步了解了CppUnit通过一组宏注册自定义测试类和自定义测试函数,并创建测试包对象的全过程。但是其中还留下了一些遗留问题,本节将彻底为你揭开剩下的谜团。
构建测试包
在构建测试包的过程中,有一个类起到了关键的作用,这就是TestSuiteBuilderContext,这是一个模板类,从TestSuiteBuilderContextBase派生,查看了这两个类的声明后,我们得到了下面的类图:
居于核心位置的TestSuiteBuilderContextBase同时聚合了TestSuite和TestFixtureFactory两个类的对象,实际上它更象是一个中间联络人,让我们看一下它的函数实现:
{
m_suite.addTest( test );
}
TestFixture * TestSuiteBuilderContextBase::makeTestFixture() const
{
return m_factory.makeFixture();
}
都是直接转交给相应的类对象。
TestSuiteBuilderContextBase中还有一个成员变量值得注意:
typedef CppUnitVector < Property > Properties;
private :
Properties m_properties;
很明显,这里用字符串的形式记录了测试包创建过程中的所有属性。
我们再来看子类:
class TestSuiteBuilderContext : public TestSuiteBuilderContextBase
{
public :
typedef Fixture FixtureType;
TestSuiteBuilderContext( TestSuiteBuilderContextBase & contextBase )
: TestSuiteBuilderContextBase( contextBase )
{
}
/* ! /brief Returns a new TestFixture instance.
* /return A new fixture instance. The fixture instance is returned by
* the TestFixtureFactory passed on construction. The actual type
* is that of the fixture on which the static method suite()
* was called.
*/
FixtureType * makeFixture() const
{
return CPPUNIT_STATIC_CAST( FixtureType * ,
TestSuiteBuilderContextBase::makeTestFixture() );
}
};
注意其中的makeFixture函数,就是直接调用基类的makeTestFixture函数,并根据实际类型进行转型,而我们知道基类的makeTestFixture函数的实现是直接转交给相应工厂的。
那么我们再看看这个工厂,都是很简单的代码:
*
* Implementation detail. Use by HelperMacros to handle TestFixture hierarchy.
*/
class TestFixtureFactory
{
public :
// ! Creates a new TestFixture instance.
virtual TestFixture * makeFixture() = 0 ;
};
/* ! /brief Concret TestFixture factory (Implementation).
*
* Implementation detail. Use by HelperMacros to handle TestFixture hierarchy.
*/
template < class TestFixtureType >
class ConcretTestFixtureFactory : public CPPUNIT_NS::TestFixtureFactory
{
/* ! /brief Returns a new TestFixture instance.
* /return A new fixture instance. The fixture instance is returned by
* the TestFixtureFactory passed on construction. The actual type
* is that of the fixture on which the static method suite()
* was called.
*/
TestFixture * makeFixture()
{
return new TestFixtureType();
}
};
了解了这些,再回过头来看suite函数的实现就很简单了。
注册测试函数
在注册测试函数的过程中,也有一个类起到了关键的作用,它就是TestCaller,这也是一个模板类。关于这个类,它的类图是:
模板的实参就是我们的自定义测试类ExampleTestCase,所以TestCaller其实是一个辅助类,负责把ExampleTestCase和TestCase关联起来。让我们看看它的函数实现:
void runTest()
{
// try {
(m_fixture ->* m_test)();
// }
// catch ( ExpectedException & ) {
// return;
// }
// ExpectedExceptionTraits<ExpectedException>::expectedException();
}
void setUp()
{
m_fixture -> setUp ();
}
void tearDown()
{
m_fixture -> tearDown ();
}
很明显,这个类是把基类TestCase要完成的工作全都原封不动就转交给我们自己的自定义测试类ExampleTestCase了,原来我们就是把这样一个偷懒的家伙加入到我们的测试包中了,本身TestCaller的类型就是TestCase,类型没有问题,但是干活的时候却没它什么事情。
注意runTest函数的实现,用函数指针的语法,一次执行一个自定义测试函数。但是这个函数是如何被调用的呢?这话说起来就长了,我们留给下回分解吧。