辅助部分(Helper)——HelperMacros


[HelperMacros]

相关文件:HelperMacros.h

这里定义了一系列宏,它们为简化CppUnit的使用提供了诸多便利。其中,宏CPPUNIT_TEST_SUITE(), 宏CPPUNIT_TEST()和宏CPPUNIT_TEST_SUITE_END()被设计用来简化创建测试包的过程。比如:

class MyTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( MyTest );
  CPPUNIT_TEST( testEquality );
  CPPUNIT_TEST( testSetName );
  CPPUNIT_TEST_SUITE_END();
public:
  void testEquality();
  void testSetName();
};

这些宏在MyTest内部定义了两个方法,第一个方法是一个名为registerTests的辅助函数,你无需直接调用。第二个方法的定义如下:

static CppUnit::TestSuite *suite();

该方法返回一个指向测试包实例的指针,该测试包对应的测试实例就是由CPPUNIT_TEST()指定的。比起手工调用suite(),使用CPPUNIT_TEST_SUITE_REGISTRATION()更为方便,它会创建一个static属性的AutoRegisterSuite类型变量,该变量将自动把测试包实例注册到NamedRegistries中。NamedRegistries中包含了所有被注册的测试包实例:

CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );
CppUnit::Test* tp =
  CppUnit::TestFactoryRegistry::getRegistry().makeTest();

上述宏还支持带模板类型参数的测试类,比如:

template<typename CharType>
class StringTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( StringTest );
  CPPUNIT_TEST( testAppend );
  CPPUNIT_TEST_SUITE_END();
public:  
  ...
};

然后,可以在StringTest类的实现代码(即.cpp文件)中加上如下代码:

CPPUNIT_TEST_SUITE_REGISTRATION( StringTest<char> );
CPPUNIT_TEST_SUITE_REGISTRATION( StringTest<wchar_t> );

下面为你逐一讲解各个宏的实现机理。同时为了便于理解,我以前面的MyTest为例,分别给出宏展开前后的代码,并在结尾处给出一个小结。首先是CPPUNIT_TEST_SUITE()的定义:

#define CPPUNIT_TEST_SUITE( ATestFixtureType )                            \
  private:                                                                \
    typedef ATestFixtureType __ThisTestFixtureType;                       \
    class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory     \
    {                                                                     \
      virtual CppUnit::TestFixture *makeFixture()                         \
      {                                                                   \
        return new ATestFixtureType();                                    \
      }                                                                   \
    };                                                                    \
  public:                                                                 \
    static void                                                           \
    registerTests( CppUnit::TestSuite *suite,                             \
                   CppUnit::TestFixtureFactory *factory )                 \
    {                                                                     \
      CppUnit::TestSuiteBuilder<__ThisTestFixtureType> builder( suite );

该宏表明开始定义一个新的测试包,它必须出现在其他宏的前面。以MyTest为例,宏展开前的代码如下:

CPPUNIT_TEST_SUITE( MyTest );

展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:

private:
  class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory
  {
    virtual CppUnit::TestFixture *makeFixture()
    {
      return new MyTest();
    }
  };
public:
  static void 
  registerTests( CppUnit::TestSuite *suite, 
                 CppUnit::TestFixtureFactory *factory )
  {
    CppUnit::TestSuiteBuilder<MyTest> builder( suite );

正如前面提到的,public部分定义了一个static属性的registerTests方法,稍后讲到CPPUNIT_TEST_SUITE_END()时即会看到其作用。此外,这里还出现了一个inner class:ThisTestFixtureFactory,它派生自TestFixtureFactory。至于TestFixtureFactory,有如下定义:

class TestFixtureFactory
{
public:
  // 创建一个新的TestFixture实例
  virtual CppUnit::TestFixture *makeFixture() =0;
};

可见ThisTestFixtureFactory只是覆盖了TestFixtureFactory的纯虚函数makeFixture,创建了一个MyTest的实例。至于makeFixture的调用时机,则是在CPPUNIT_TEST()处。以下是CPPUNIT_TEST()的定义:

#define CPPUNIT_TEST( testMethod )                                           \
      builder.addTestCaller( #testMethod,                                    \
                             &__ThisTestFixtureType::testMethod ,            \
                             (__ThisTestFixtureType*)factory->makeFixture() ) 

该宏用于每次向测试包添加一个测试方法,测试方法必须满足形参和返回值都为void类型。以MyTest的testEquality为例,宏展开前的代码如下:

CPPUNIT_TEST( testEquality );

展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:

    builder.addTestCaller( "testEquality", 
                           &MyTest::testEquality, 
                           (MyTest*)factory->makeFixture() ) 

这里调用了TestSuiteBuilder的addTestCaller,在TestSuiteBuilder内部所维护的m_suite中添加了一个测试实例,确切地说是一个TestCaller,该TestCaller是专门用来测试testEquality方法的,这也是为什么测试方法必须满足形参和返回值都为void类型的原因。关于TestSuiteBuilder和TestCaller,前面已经有过叙述了。

再来看一下CPPUNIT_TEST_SUITE_END()的定义:

#define CPPUNIT_TEST_SUITE_END()                                          \
      builder.takeSuite();                                                \
    }                                                                     \
    static CppUnit::TestSuite *suite()                                    \
    {                                                                     \
      CppUnit::TestSuiteBuilder<__ThisTestFixtureType>                    \
          builder __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType );          \
      ThisTestFixtureFactory factory;                                     \
      __ThisTestFixtureType::registerTests( builder.suite(), &factory );  \
      return builder.takeSuite();                                         \
    }                                                                     \
  private: /* 此处的typedef并无实际作用,只是为了使该宏可以以分号结束 */            \
    typedef ThisTestFixtureFactory __ThisTestFixtureFactory                   

该宏表明一个测试包定义的结束,因为末尾那个无关痛痒的typdef,其后的成员变量隐式情况下都将具有private属性。__CPPUNIT_SUITE_CTOR_ARGS()宏稍后会提到,此处并不影响理解,以MyTest为例,宏展开前的代码如下:

CPPUNIT_TEST_SUITE_END();

展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:

    builder.takeSuite();
  }
  static CppUnit::TestSuite *suite()
  {
    CppUnit::TestSuiteBuilder<MyTest> builder;
    ThisTestFixtureFactory factory;
    MyTest::registerTests( builder.suite(), &factory );
    return builder.takeSuite();
  }

这里出现了前面曾经提到的static属性的suite方法,它在构建了TestSuiteBuilder和ThisTestFixtureFactory之后,便开始调用前面提到的那个registerTests方法,其结果是向builder内含的m_suite中加入若干TestCaller实例。最后,它将返回指向该m_suite实例的指针。

还有一个需要提到的关键的宏是CPPUNIT_TEST_SUITE_REGISTRATION(),其定义如下:

#define CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType )      \
  static CppUnit::AutoRegisterSuite< ATestFixtureType >          \
             __CPPUNIT_MAKE_UNIQUE_NAME(__autoRegisterSuite )

该宏声明了一个static属性的全局变量,该变量将自动把测试包实例注册到NamedRegistries中。__CPPUNIT_MAKE_UNIQUE_NAME()宏稍后会提到,此处并不影响理解,以MyTest为例,宏展开前的代码如下:

CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );

展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:

// __autoRegisterSuite123是一个变量名,后面的123代表了当前代码行号,
// 至于该变量名是如何产生的,__CPPUNIT_MAKE_UNIQUE_NAME部分将会提到
static CppUnit::AutoRegisterSuite< MyTest > __autoRegisterSuite123

以下就上面的内容给出一个小结。类MyTest在宏展开前的代码大致如下:

// MyTest.h
class MyTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( MyTest );
  CPPUNIT_TEST( testEquality );
  CPPUNIT_TEST_SUITE_END();
public:
  void testEquality();
};

// MyTest.cpp
CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );

展开后的代码如下:

// MyTest.h
class MyTest : public CppUnit::TestFixture {
private:
  class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory
  {
    virtual CppUnit::TestFixture *makeFixture()
    {
      return new MyTest();
    }
  };
public:
  static void 
  registerTests( CppUnit::TestSuite *suite, 
                 CppUnit::TestFixtureFactory *factory )
  {
    CppUnit::TestSuiteBuilder<MyTest> builder( suite );
    builder.addTestCaller( "testEquality", 
                           &MyTest::testEquality, 
                           (MyTest*)factory->makeFixture() ) 
    builder.takeSuite();
  }
  static CppUnit::TestSuite *suite()
  {
    CppUnit::TestSuiteBuilder<MyTest> builder;
    ThisTestFixtureFactory factory;
    MyTest::registerTests( builder.suite(), &factory );
    return builder.takeSuite();
  }
public:
  void testEquality();
};

// MyTest.cpp
static CppUnit::AutoRegisterSuite< MyTest > __autoRegisterSuite123

并在适当的地方,添加如下代码:

CppUnit::TestFactoryRegistry &registry = 
    CppUnit::TestFactoryRegistry::getRegistry(); // 对应“All Tests”的注册项
registry.makeTest();

由此得到测试用例创建的完整流程如下:

  1. 定义AutoRegisterSuite<MyTest>类型的static全局变量,在其ctor中完成向NamedRegistries的注册,即在NamedRegistries中注册一个名为“All Tests”的注册项,并在其下注册一个TestSuiteFactory<MyTest>类型的实例(但该实例并未放在NamedRegistries中)
  2. 取得NamedRegistries中的对应“All Tests”的注册项,并调用makeTest方法
    • 2.1 创建一个TestSuite的实例
    • 2.2 得到那个TestSuiteFactory<MyTest>类型的实例,并调用其makeTest方法,以得到一个真正的测试用例
    • 2.3 将测试用例添加到先前定义的那个TestSuite实例中(通过调用其addTest方法实现)

关于2.2,有进一步的说明:

TestSuiteFactory<MyTest>的makeTest方法事实上调用了MyTest的suite方法,而该suite方法正是前面运用CPPUNIT_TEST_SUITE_END()所产生的,进入suite之后,流程如下:

  1. 创建TestSuiteBuilder<MyTest>类型和ThisTestFixtureFactory类型的变量builder、factory
  2. 将builder内含的测试包m_suite和factory作为实参,调用registerTests方法
  3. 调用TestSuiteBuilder<MyTest>的addTestCaller,创建一个TestCaller实例,该实例对应一个测试方法(比如:本例中的testEquality)

至此,整个测试用例的创建过程圆满结束。并且,如果使用多个CPPUINT_TEST宏即可注册多个测试方法,每个测试方法都有一个TestCaller的实例与之对应。

再来看看HelperMacros中的其他几个宏。与CPPUNIT_TEST_SUITE()类似的另一个宏是CPPUNIT_TEST_SUB_SUITE(),它同样是用于表明开始定义一个新的测试包,不过它仅在如下场合使用:基类中已经通过使用CPPUNIT_TEST_SUITE()或CPPUNIT_TEST_SUB_SUITE()定义了测试包。其定义如下:

#define CPPUNIT_TEST_SUB_SUITE( ATestFixtureType, ASuperClass )  \
  private:                                                       \
    typedef ASuperClass __ThisSuperClassType;                    \
    CPPUNIT_TEST_SUITE( ATestFixtureType );                      \
      __ThisSuperClassType::registerTests( suite, factory )

此处首先调用了CPPUNIT_TEST_SUITE(),随后是调用基类的registerTests方法,如此一来,基类中定义的所有TestCaller都将被自动添加到当前派生类中所定义的这个测试包中,即最后的结果是:当前定义的测试包中包含了基类和派生类中出现的所有测试方法。来看一个使用CPPUNIT_TEST_SUB_SUITE()的例子:

class MySubTest : public MyTest {
  CPPUNIT_TEST_SUB_SUITE( MySubTest, MyTest );
  CPPUNIT_TEST( testAdd );
  CPPUNIT_TEST( testSub );
  CPPUNIT_TEST_SUITE_END();
public:
  void testAdd();
  void testSub();
};

与CPPUNIT_TEST()类似的还有另两个宏,它们分别是CPPUNIT_TEST_EXCEPTION()和CPPUNIT_TEST_FAIL()。CPPUNIT_TEST_EXCEPTION()也用于向测试包添加一个测试方法,与CPPUNIT_TEST()不同的是,它指定了一个预期会抛出的异常,若该异常成功产生,则一切照旧,否则便会导致测试失败。其定义如下:

#define CPPUNIT_TEST_EXCEPTION( testMethod, ExceptionType )                   \
      builder.addTestCallerForException( #testMethod,                         \
                             &__ThisTestFixtureType::testMethod ,             \
                             (__ThisTestFixtureType*)factory->makeFixture(),  \
                             (ExceptionType *)NULL ); 

来看一个使用CPPUNIT_TEST_EXCEPTION()的例子:

#include <vector>
class MyTest : public CppUnit::TestFixture {
  CPPUNIT_TEST_SUITE( MyTest );
  CPPUNIT_TEST_EXCEPTION( testVectorAtThrow, std::invalid_argument );
  CPPUNIT_TEST_SUITE_END();
public:
  void testVectorAtThrow()
  {
    std::vector<int> v;
    v.at( 1 );     // 必将抛出std::invalid_argument异常
  }
};

CPPUNIT_TEST_FAIL()与CPPUNIT_TEST_EXCEPTION()很相似,其定义如下:

#define CPPUNIT_TEST_FAIL( testMethod ) \
              CPPUNIT_TEST_EXCEPTION( testMethod, CppUnit::Exception )

该宏用于向测试包添加一个测试方法,并预期其必定会失败,事实上就是预期其必定会抛出CppUnit::Exception异常。通常,如果在测试用例中用到了与断言相关的宏时,就可以使用该宏了,关于“与断言相关的宏”请见core部分。来看一个使用CPPUNIT_TEST_FAIL()的例子:

CPPUNIT_TEST_FAIL( testAssertFalseFail );

void testAssertFalseFail()
{
  CPPUNIT_ASSERT( false );
}

与CPPUNIT_TEST_SUITE_REGISTRATION()类似的另一个宏是CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()。它们都会创建一个AutoRegisterSuite类型的static全局变量,并在其ctor中,将一个新创建的测试包的类工厂实例注册到NamedRegistries中。所不同的是,CPPUNIT_TEST_SUITE_REGISTRATION()没有指定注册项的名字,代之以缺省的“All Tests”,而CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()则通过第二个参数指定了注册项的名字。其定义如下:

#define CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName ) \
  static CppUnit::AutoRegisterSuite< ATestFixtureType >                      \
             __CPPUNIT_MAKE_UNIQUE_NAME(__autoRegisterSuite )(suiteName)

来看一个使用CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()的例子:

// MySuites.h
namespace MySuites {
  std::string math() { 
    return "Math";
  }
}

// ComplexNumberTest.cpp
#include "MySuites.h"

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ComplexNumberTest, MySuites::math() );

上面的例子中使用了一个static方法返回测试包的名称,比起直接在代码中引入代表测试包名称的字符串(也就是所谓的Hardcoded String),这样的方式更为灵活。它能使你免于因为拼写错误而无法获得正确的注册项名称。

最后,稍带提一下几个次要的宏。在CPPUNIT_TEST_SUITE()中定义TestSuiteBuilder实例时曾经用到过__CPPUNIT_SUITE_CTOR_ARGS()。该宏在CPPUNIT_USE_TYPEINFO_NAME被定义的时候(即你所使用的编译器支持RTTI机制),不起任何作用;否则,将返回一个字符串。至于该字符串的作用,请见TestSuiteBuilder部分的讲解。

#if CPPUNIT_USE_TYPEINFO_NAME
#  define __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType )
#else
#  define __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType ) (std::string(#ATestFixtureType))
#endif

在CPPUNIT_TEST_SUITE_REGISTRATION()和CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()中曾经用到过__CPPUNIT_MAKE_UNIQUE_NAME()。该宏的作用是产生一个唯一的全局性名字,它有一个参数str,将str和__LINE__“粘合”在一起便是最终的结果。其中,__LINE__是一个隐含的静态变量,代表了当前代码在文件中所处的行号。

#define __CPPUNIT_CONCATENATE_DIRECT( s1, s2 ) s1##s2
#define __CPPUNIT_CONCATENATE( s1, s2 ) __CPPUNIT_CONCATENATE_DIRECT( s1, s2 )

#define __CPPUNIT_MAKE_UNIQUE_NAME( str ) __CPPUNIT_CONCATENATE( str, __LINE__ )

返回目录