辅助部分(Helper)——创建机制


这一部分提供了一些辅助类,多数与创建Test类的实例有关,其中包括用于创建Test的工厂类,用于管理工厂类的注册类,可以单独运行某个测试的TestCaller,还有为方便使用而定义的一组宏。

[TypeInfoHelper]

相关文件:TypeInfoHelper.h,TypeInfoHelper.cpp

为了扫清理解障碍,TypeInfoHelper是首先需要解释的。该类的作用是根据指定类的type_info返回一个代表其类名的字符串。为了使用此功能,你必须定义CPPUNIT_USE_TYPEINFO_NAME宏,即你必须确认你所使用的c++编译器提供了type_info机制。TypeInfoHelper仅有一个static成员函数getClassName,请留意morning的注释:

std::string 
TypeInfoHelper::getClassName( const std::type_info &info )
{
  static std::string classPrefix( "class " );
  std::string name( info.name() );      // 调用info的name以得到类名信息

  // 确定类名中是否有"class"字样
  bool has_class_prefix = 0 ==
#if CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST
    name.compare( classPrefix, 0, classPrefix.length() );
#else
    name.compare( 0, classPrefix.length(), classPrefix );
#endif

  // 返回不带有"class"字样的类名
  return has_class_prefix ? name.substr( classPrefix.length() ) : name;
}

关于此处用到的std::string::compare函数,在bcb和vc中的调用方式不一样,所以就有了CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST宏。参见config-msvc6.h和config-bcb5.h中的相关定义以及portability部分的说明。

[TestFactory]

相关文件:TestFactory.h

是Test的抽象类工厂(Abstract Factory),用于创建一个Test实例,它仅仅包含了一个纯虚函数makeTest的声明:

virtual Test* makeTest() = 0;

[TestFactoryRegistry,NamedRegistries]

相关文件:TestFactoryRegistry.cpp,TestFactoryRegistry.cpp

某次测试的运行可能包含了许多测试实例,它们彼此间可能呈现层状结构,而每个测试实例的创建都是由某个与之对应的类工厂完成的。为了较好的管理这些类工厂,实现其生命周期的自动操控,CppUnit采用了一种注册机制。类TestFactoryRegistry和类NamedRegistries就是用来实现该机制的。

NamedRegistries是一个管理类,用以管理所有的注册项——TestFactoryRegistry类的实例,由它全权负责TestFactoryRegistry的生命周期。TestFactoryRegistry在稍后会讲到。

NamedRegistries采用了Singleton Pattern,以保证其“全局性”的唯一访问点。此处是通过在函数内定义静态变量的方式来实现的:

NamedRegistries &
NamedRegistries::getInstance()
{
  static NamedRegistries namedRegistries;
  return namedRegistries;
}

NamedRegistries内部有三个private属性的成员变量:

Registries m_registries;        // 代表一个注册名称-注册项的映射表
Factories m_factoriesToDestroy; // 代表即将被销毁的注册项序列
Factories m_destroyedFactories; // 代表已经被销毁的注册项序列

其中,Registries和Factories的定义如下:

typedef std::map<std::string, TestFactoryRegistry *> Registries;
typedef std::set<TestFactory *> Factories;

为了使外界可以访问到注册项,NamedRegistries提供了getRegistry方法,请留意morning的注释:

TestFactoryRegistry &
NamedRegistries::getRegistry( std::string name )
{
  // 根据name在m_registries中查找注册项
  Registries::const_iterator foundIt = m_registries.find( name );
  // 若没有找到,则创建一个TestFactoryRegistry实例,并赋以name作为名称
  // 将之分别插入m_registries和m_factoriesToDestroy中
  // 再返回该TestFactoryRegistry实例
  if ( foundIt == m_registries.end() )
  {
    TestFactoryRegistry *factory = new TestFactoryRegistry( name );
    m_registries.insert( std::make_pair( name, factory ) );
    m_factoriesToDestroy.insert( factory );
    return *factory;
  }
  // 若找到,则直接返回
  return *foundIt->second;
}

在NamedRegistries被销毁(即dtor被调用)的同时,其下所属的TestFactoryRegistry实例也将被销毁:

NamedRegistries::~NamedRegistries()
{
  Registries::iterator it = m_registries.begin();
  while ( it != m_registries.end() )
  {
    TestFactoryRegistry *registry = (it++)->second;
    if ( needDestroy( registry ) )
      delete registry;
  }
}

这里加上needDestroy的判断是为了防止出现多次销毁同一个TestFactoryRegistry实例的现象,稍后可以发现这和TestFactoryRegistry的dtor实现有关,另外一个wasDestroyed方法,也与此有关,它们的实现代码分别如下:

void 
NamedRegistries::wasDestroyed( TestFactory *factory )
{
  // 从m_factoriesToDestroy中摘除factory
  m_factoriesToDestroy.erase( factory );
  // 将factory插入m_destroyedFactories
  m_destroyedFactories.insert( factory );
}

bool 
NamedRegistries::needDestroy( TestFactory *factory )
{
  // 判断m_destroyedFactories是否存在factory
  return m_destroyedFactories.count( factory ) == 0;
}

根据约定,TestFactory的注册项必须调用wasDestroyed方法,以表明一个TestFactoryRegistry实例已经被成功销毁了。同时,它也需要调用needDestroy以确信一个给定的TestFactory可以被允许销毁,即事先没有被其他TestFactoryRegistry实例销毁。

我们再来看看TestFactoryRegistry。其ctor只是简单的将传入其中的字符串赋给成员变量m_name,它代表了注册项的名称:

TestFactoryRegistry::TestFactoryRegistry( std::string name ) :
    m_name( name )
{
}

dtor稍微复杂一些,请留意morning的注释:

TestFactoryRegistry::~TestFactoryRegistry()
{
  // 还记得前面提到的约定吗?
  NamedRegistries::getInstance().wasDestroyed( this );

  // 遍历其下所属的各个TestFactory实例
  for ( Factories::iterator it = m_factories.begin(); it != m_factories.end(); ++it )
  {
    TestFactory *factory = it->second;
    // 若factory没有存在于NamedRegistries::m_destroyedFactories中
    // 则可以放心销毁之。factory的销毁可能形成连锁反应,亦即,若factory本身
    // 也是TestFactoryRegistry类型的,其dtor又将被调用,上述过程将再次重现
    if ( NamedRegistries::getInstance().needDestroy( factory ) )
      delete factory;
  }
}

这里我们再次看到了wasDestroyed和needDestroy,正如前面所述,它们是为了防止多次销毁同一个TestFactoryRegistry实例的。对于如下的代码:

  registerFactory( "All Tests", getRegistry( "Unit Tests" ) );

在了解了TestFactoryRegistry::getRegistry的实际行为之后,你会发现,名为“Unit Tests”的注册项将同时被名为"All Tests"的注册项和NamedRegistries所拥有。此外,morning以为,对于没有在NamedRegistries中注册的TestFactoryRegistry实例,调用needDestroy的结果同样为true,此处也保证可以被及时销毁,而不致造成内存泄漏。由此足见,此处的注册机制是相当灵活的。

上面出现的m_factories是TestFactoryRegistry的一个private属性的成员变量:

Factories m_factories;  // 代表一个类工厂名称-类工厂实例的映射表

Factories的定义如下:

typedef std::map<std::string, TestFactory *> Factories;

至于getRegistry方法,则有两个版本,一个有形参,另一个则没有:

TestFactoryRegistry &
TestFactoryRegistry::getRegistry()
{
  // 调用另一个版本的getRegistry,并传入“All Tests”
  // 一般代表某组测试的“根节点”
  return getRegistry( "All Tests" );
}

TestFactoryRegistry &
TestFactoryRegistry::getRegistry( const std::string &name )
{
  // 获取NamedRegistries的实例,并调用其getRegistry方法
  return NamedRegistries::getInstance().getRegistry( name );
}

前面提到,某个类工厂的注册项,其下可能包含一组类工厂,即形成所谓的层状结构。为了支持这一功能,TestFactoryRegistry提供了registerFactory方法,同样有两个不同版本:

// 给定类工厂的名称及其对应的实例指针
void 
TestFactoryRegistry::registerFactory( const std::string &name,
                                      TestFactory *factory )
{
  m_factories[name] = factory;
}

// 只给出了类工厂的实例指针,此之渭Unnamed TestFactory
void 
TestFactoryRegistry::registerFactory( TestFactory *factory )
{
  // 通过serialNumber自动形成名称,再调用另一个版本的registerFactory
  // static变量serialNumber从1开始,每次累加1
  static int serialNumber = 1;

  OStringStream ost;
  ost << "@Dummy@" << serialNumber++;

  registerFactory( ost.str(), factory );
}

当层状结构构建好后,就可以调用makeTest方法,创建待运行的测试实例了,请留意morning的注释:

Test *
TestFactoryRegistry::makeTest()
{
  // 创建一个测试包,并冠以m_name的名称
  TestSuite *suite = new TestSuite( m_name );
  // 调用addTestToSuite,以将其下所属的测试实例添加到suite中
  addTestToSuite( suite );
  // 返回测试包对应的指针
  return suite;
}

void 
TestFactoryRegistry::addTestToSuite( TestSuite *suite )
{
  // 遍历其下所属的各个TestFactory实例
  for ( Factories::iterator it = m_factories.begin(); 
        it != m_factories.end(); 
        ++it )
  {
    TestFactory *factory = (*it).second;
    // 调用factory的makeTest方法创建测试实例
    // 将指向实例的指针添加到suite中
    // makeTest的调用可能形成连锁反应
    suite->addTest( factory->makeTest() );
  }
  // 当所有的factory都遍历完后,即所有的测试实例都被创建成功后
  // 整个测试实例的层状结构也就构建成功了
}

以下对CppUnit的注册机制作一个简单的小结:

  • NamedRegistries以“线性”方式管理着所有的类工厂注册项——TestFactoryRegistry,负责维护其生命周期
  • 一个TestFactoryRegistry对应一个类工厂实例,其下可能包含一系列的类工厂实例,构成层状结构
  • TestFactory(s)可以注册到NamedRegistries中,也可以不注册,它们要么被NamedRegistries销毁,要么被其所属之TestFactoryRegistry销毁
  • 所有的类工厂都注册完毕后,TestFactoryRegistry::makeTest方法的一次调用,将形成连锁反映(即递归调用),以创建一组具有层状结构的测试实例

以下提供几个使用TestFactoryRegistry类的例子,以加深认识:

// 例1:创建一个空的测试包,并将与之对应的类工厂注册项注册到NamedRegistries中
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
CppUnit::TestSuite *suite = registry.makeTest();

// 例2:创建一个名为“Math”的测试包,并将与之对应的类工厂注册项注册到NamedRegistries中
CppUnit::TestFactoryRegistry &mathRegistry = CppUnit::TestFactoryRegistry::getRegistry( "Math" );
CppUnit::TestSuite *mathSuite = mathRegistry.makeTest();

// 例3:创建一个名为“All tests”的测试包,并将名为“Graph”和“Math”的测试包作为“All tests”测试包的子项
// 与全部三个测试包对应的类工厂注册项都被注册到NamedRegistries中
CppUnit::TestSuite *rootSuite = new CppUnit::TestSuite( "All tests" );
rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Graph" ).makeTest() );
rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Math" ).makeTest() );
CppUnit::TestFactoryRegistry::getRegistry().addTestToSuite( rootSuite );

// 例4:例3的另一中实现方式
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
registry.registerFactory( CppUnit::TestFactoryRegistry::getRegistry( "Graph" ) );
registry.registerFactory( CppUnit::TestFactoryRegistry::getRegistry( "Math" ) );
CppUnit::TestSuite *suite = registry.makeTest();

[TestSuiteFactory]

相关文件:TestSuiteFactory.h

模板类,派生自TestFactory,是TestFixture的类工厂,并且该TestFixture必须实现一个静态的suite方法,以便在覆盖TestFactory的makeTest时调用:

// 此处的TestCaseType就是一个TestFixture
template<typename TestCaseType>
class TestSuiteFactory : public TestFactory
{
public:
  virtual Test *makeTest()
  {
    return TestCaseType::suite();
  }
};

[TestSuiteBuilder]

相关文件:TestSuiteBuilder.h

模板类,用以将一系列测试添加到一个测试包中。所有加入该测试包的测试,其固有名称之前都会被加上测试包的名称,形成类似如下的测试名称:MyTestSuiteName.myTestName,前者为测试包的名称,后者为测试本身的名称。

TestSuiteBuilder内部维护了一个m_suite指针以指向对应的测试包实例,这是一个Smart Pointer,因此其生命周期无需手工操控,而是由TestSuiteBuilder来维护:

std::auto_ptr<TestSuite> m_suite;

至于m_suite所指的对象,可以由TestSuiteBuilder自己创建,也可以从外面传入,全凭你选择调用ctor的哪个版本了:

// 使用type_info生成测试包的名称
#if CPPUNIT_USE_TYPEINFO_NAME
TestSuiteBuilder() : 
    m_suite( new TestSuite( 
        TypeInfoHelper::getClassName( typeid(Fixture) )  ) )
{
}
#endif

TestSuiteBuilder( TestSuite *suite ) : m_suite( suite ) 
{
}

TestSuiteBuilder(std::string name) : m_suite( new TestSuite(name) ) 
{
}

添加测试的方法是简单地调用m_suite的addTest:

void addTest( Test *test )
{
  m_suite->addTest( test );
}

此外,为了方便使用,TestSuiteBuilder还提供了几个用于添加TestCaller的方法,它们调用makeTestName以生成测试名称,最终都将调用addTest。其中,Fixture是TestSuiteBuilder的模板类型参数,TestMethod的定义如下:

typedef void (Fixture::*TestMethod)();

至于TestCaller,稍后会讲到:

void addTestCaller(std::string methodName, 
                   TestMethod testMethod )
{
  Test *test = 
      new TestCaller<Fixture>( makeTestName( methodName ), 
                               testMethod );
  addTest( test );
}

void addTestCaller(std::string methodName, 
                   TestMethod testMethod, 
                   Fixture *fixture )
{
  Test *test = 
      new TestCaller<Fixture>( makeTestName( methodName ), 
                               testMethod,
                               fixture);
  addTest( test );
}

template<typename ExceptionType>
void addTestCallerForException(std::string methodName, 
                               TestMethod testMethod, 
                               Fixture *fixture,
                               ExceptionType *dummyPointer )
                               // dummyPointer本身没有实际作用,此处只为获取其所属类型
{
  Test *test = new TestCaller<Fixture,ExceptionType>( 
                               makeTestName( methodName ), 
                               testMethod,
                               fixture);
  addTest( test );
}

makeTestName的定义如下:

std::string makeTestName( const std::string &methodName )
{
  return m_suite->getName() + "." + methodName;
}

为了便于外界访问m_suite指针,TestSuiteBuilder还提供了如下辅助方法:

TestSuite *suite() const
{
  return m_suite.get();
}

TestSuite *takeSuite()
{
  return m_suite.release();
}

[TestCaller]

相关文件:TestCaller.h

在前面以及core部分曾经多次提到TestCaller,此类的作用是根据一个fixture创建一个测试用例。当你需要单独运行某个测试,或者要将其添加到某个测试包中时,你就可以使用TestCaller。一个TestCaller仅对应一个Test类,该Test类和一个TestFixture相关联。下面是一个演示的例子:

// 一个TestFixture,并包含了test method(s)
class MathTest : public CppUnit::TestFixture {
    ...
  public:
    void         setUp();
    void         tearDown();

    void         testAdd();
    void         testSubtract();
};

CppUnit::Test *MathTest::suite() {
  CppUnit::TestSuite *suite = new CppUnit::TestSuite;

  // 将MathTest::testAdd加入TestCaller,并将该TestCaller加入测试包中
  suite->addTest( new CppUnit::TestCaller<MathTest>( "testAdd", testAdd ) );
  return suite;
}

你可是使用TestCaller,将任意一个test方法和某个TestFixture绑定在一起,只要该test方法满足如下形式的定义:

void testMethod(void);

TestCaller其实是一个模板类,它派生自TestCase,有两个模板类型参数,前一个参数代表了TestFixture类,后一个参数代表某个异常类,缺省类型为NoExceptionExpected,至于该参数的作用,稍后便知分晓。关于TestCase,请见core部分:

template <typename Fixture, 
	  typename ExpectedException = NoExceptionExpected>
class TestCaller : public TestCase
{
  //...
};

TestCaller有三个private属性的成员变量:

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

TestMethod的定义如下:

typedef void (Fixture::*TestMethod)();

正是因为有如上定义,才限制了TestCaller只能支持形参和返回值均为为void类型的test方法。

来看一下TestCaller的ctor和dtor,你会发现,对于Fixture的安置工作,CppUnit的实现者可谓细心周到:

// 由TestCaller创建fixture,负责维护其生命周期
TestCaller( std::string name, TestMethod test ) :
            TestCase( name ), 
            m_ownFixture( true ),
            m_fixture( new Fixture() ),
            m_test( test )
{
}

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

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

// 根据m_ownFixture,决定是否销毁m_fixture
~TestCaller() 
{
  if (m_ownFixture)
    delete m_fixture;
}

作为TestCaller的基类,TestCase派生自Test和TestFixture,因此TestCaller有义务实现如下三个虚函数。

void setUp()
{
  // 简单地调用了m_fixture的setUp方法
  m_fixture->setUp ();
}

void tearDown()
{
  // 简单地调用了m_fixture的tearDown方法
  m_fixture->tearDown ();
}

void runTest()
{
  // 调用了m_fixture的一个test方法
  // 由此可见:
  // - test方法必须在Fixture类中定义
  // - 若Fixture类中存在多个test方法,则需一一建立与之对应的TestCaller
  try {
    (m_fixture->*m_test)();
  }
  catch ( ExpectedException & ) {
    return;
  }

  ExpectedExceptionTraits<ExpectedException>::expectedException();
}

这里不得不提到辅助类ExpectedExceptionTraits,还有前面出现过的NoExceptionExpected,它们和TestCaller一起被定以在同一个文件里。

template<typename ExceptionType>
struct ExpectedExceptionTraits
{
  static void expectedException()
  {
#if CPPUNIT_USE_TYPEINFO_NAME
    std::string message( "Expected exception of type " );
    message += TypeInfoHelper::getClassName( typeid( ExceptionType ) );
    message += ", but got none";
#else
    std::string message( "Expected exception but got none" );
#endif
    throw Exception( message );
  }
};

ExpectedExceptionTraits只有一个static方法,其唯一的作用是抛出一个Exception类型的异常,并附带一个说明信息,指出某个预计产生的异常并未出现,该预计的异常由ExpectedExceptionTraits的模板类型参数来指定。关于Exception,请见core部分。结合前面出现过的TestCaller::runTest的行为,我们可以得出如下结论:

通常情况下,如果调用m_fixture的test方法时,没有抛出任何异常,或者抛出的不是ExpectedException类型的异常,则ExpectedExceptionTraits的expectedException方法会产生一个异常来指出这一错误。其中的ExpectedException,由TestCaller的第二个类型参数来指定。

再看一下NoExceptionExpected的定义,它被作为TestCaller的第二个类型参数的缺省类型:

class CPPUNIT_API NoExceptionExpected
{
private:
  // 防止此类被实例化
  NoExceptionExpected();
};

什么事都不做!是的,NoExceptionExpected只是一个Marker class,它用来表明,TestCaller在任何时候都不会对运行test方法时所抛出的异常做预期性检查。当然,光有了NoExceptionExpected还不够,还需要定义一个ExpectedExceptionTraits的特化版本:

template<>
struct ExpectedExceptionTraits<NoExceptionExpected>
{
  static void expectedException()
  {
  }
};

同样是什么事都没做,再次结合TestCaller::runTest的行为,我们就可以得出如下结论:

当TestCaller的第二个类型参数为NoExceptionExpected时,如果调用m_fixture的test方法时

  • 没有抛出任何异常,则ExpectedExceptionTraits的expectedException方法(特化版)将被调用,并且不抛出任何异常;
  • 有异常被抛出,则一定不是NoExceptionExpected(因为那个private ctor),原样继续向外抛出此异常。

[AutoRegisterSuite]

相关文件:AutoRegisterSuite.h

AutoRegisterSuite是一个模板类,其作用是自动注册指定类型的测试包,不过你不需要直接使用该类,而代之以如下的两个宏:

CPPUNIT_TEST_SUITE_REGISTRATION()
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()

关于宏,在随后的HelperMacros部分将会有更为详细的说明。

AutoRegisterSuite的全部内容是两个不同版本的ctor:

AutoRegisterSuite()
{
  // 利用TestSuiteFactory创建一个类工厂实例factory
  TestFactory *factory = new TestSuiteFactory<TestCaseType>();
  // 调用getRegistry(),在NamedRegistries中注册一个TestFactoryRegistry实例,
  // 注册项的名称为“All Tests”,并调用该实例的registerFactory,将factory注册为
  // 其下所属的一个类工厂实例
  TestFactoryRegistry::getRegistry().registerFactory( factory );
}

AutoRegisterSuite( const std::string &name )
{
  // 除了注册项的名称由外部指定外,其余同前
  TestFactory *factory = new TestSuiteFactory<TestCaseType>();
  TestFactoryRegistry::getRegistry( name ).registerFactory( factory );
}

这里的TestCaseType是AutoRegisterSuite的模板类型参数。前面曾经提到过的TestSuiteFactory,其makeTest方法会调用TestCaseType的suite方法以创建测试实例,至于makeTest的调用时机,则和TestFactoryRegistry的makeTest方法有关。

返回目录