构建测试包

HTML clipboard

前言

    我们要分析的源码样本就是simple工程,因为这个工程顾名思义,肯定是最简单的嘛。这个工程里的源码主要包括两个部分:

  • main函数
  • ExampleTestCase类

    main函数比较复杂,我们先跳过这部分,先看ExampleTestCase类。

ExampleTestCase类

    这个类派生自TestFixture,上一章我们已经说过了,这种方式自定义测试类比较简单。先看看基类的声明:

class CPPUNIT_API 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类的声明:

class ExampleTestCase : public CPPUNIT_NS::TestFixture
{
  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函数重载了,查一下实现:

void ExampleTestCase::setUp()
{
  m_value1 
= 2.0;
  m_value2 
= 3.0;
}

    仅仅是初始化两个成员变量。基类的另一个虚函数tearDown没有重载,还是空。

    再看看其它几个自定义的测试函数,都比较简单。

    真正有问题的是开头的那几个宏,这几个宏负责构建我们的测试包,本章将详细的讲述这几个宏,揭示它们背后的秘密。

三个宏定义

CPPUNIT_TEST_SUITE

    切换到CPPUNIT_TEST_SUITE的宏定义上,真是超长的宏定义啊:

 1 #define CPPUNIT_TEST_SUITE( ATestFixtureType )                              \
 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那里学来的。

  1. 代码行3,首先把我们自己的测试用例ExampleTestCase重新声明为TestFixtureType类型,就是变个名称而已,使宏看起来好看一点
  2. 代码行6,然后,声明一个静态的私有函数getTestNamer__,得到测试用例的名字
  3. 代码行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"

  4. 代码行13-14,把TestSuiteBuilderContext类型重新声明为TestSuiteBuilderContextType,又是变个名称而已,但是TestSuiteBuilderContext又是什么东西?从名称上来看,很明显,这 里采用了Builder设计模式,先放放,回头我们再细看。
  5. 代码行16-17,再声明一个静态的公有函数addTestsToSuite,加入测试用例到测试组,参数是什么?就是一个TestSuiteBuilderContextBase,看起来这家伙 就是Builder设计模式中所有ConcreteBuilder的基类了
  6. 代码行19,声明了一个对象context,类型是一个ConcreteBuilder,参数就是baseContext,ConcreteBuilder的基类,注意,这个函数没有实现完嘛,因为没有结束的},很明显,这是为了需要后续的宏继续写下去

    总结一下,这个宏用getTestNamer__函数注册了自定义测试类的名称作为标识,然后用addTestsToSuite函数开始注册自定义测试函数

CPPUNIT_TEST

    切换到CPPUNIT_TEST的宏定义上:


1 #define CPPUNIT_TEST( testMethod )                        \
2     CPPUNIT_TEST_SUITE_ADD_TEST(                           \
3         ( new CPPUNIT_NS::TestCaller<TestFixtureType>(    \
4                   context.getTestNameFor( #testMethod),   \
5                   &TestFixtureType::testMethod,           \
6                   context.makeFixture() ) ) )
  1. 代码行2,先看CPPUNIT_TEST_SUITE_ADD_TEST的宏定义
    #define CPPUNIT_TEST_SUITE_ADD_TEST( test ) \
          context.addTest( test )

    还好,这句好懂,就是给上下文加一个测试用例嘛。

  2. 代码行3,就是new一个TestCaller而已 ,这个类我们上一章说过一点,这是一个辅助类,但是它到底是如何起作用的呢?待查
  3. 代码行4,又是一个名称相关的东西,一个‘#’号就暴露无疑了,这里传入了测试参数的名称字符串
  4. 代码行5,一个指向成员函数的指针,还好,没忘了这么偏的语法,不过想想也是,这里很明显是要注册每个自定义测试函数的信息嘛,传入一个函数名称用于标识,传入一个函数指针用于执行
  5. 代码行6,这里让context上下文创建测试装置

    总结一下,这个宏用TestCaller类注册了一个测试函数的信息,包括函数名称和函数指针,然后再加入到context上下文 中。

CPPUNIT_TEST_SUITE_END

    切换到CPPUNIT_TEST_SUITE_END的宏定义上:

 1 #define 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__

    我再晕,又是这么长的一个宏啊。

  1. 代码行2,终于看到addTestsToSuite函数结束的标志了
  2. 代码行4,一波未平一波又起啊,又搞出来一个suite函数,返回测试包对象的指针
  3. 代码行6,首先得到测试类的名字,这个函数我们刚才看过了
  4. 代码行7-8,然后用这个测试类的名字构造一个测试包的对象,这里用到了智能指针,当然是为了一旦有问题不用手动delete了
  5. 代码行9,一个ConcretTestFixtureFactory,一个测试类的工厂 ,注意这里是模板类,类型就是自定义测试类,居然是工厂设计模式,看样子应该是工厂方法设计模式吧,待查
  6. 代码行10-12,咦,前面看到过的Builder基类怎么又来凑热闹了,不过也别说,这家伙要干的事情也真是麻烦,参数这么多,他要求有测试包对象,测试包名称,测试类工厂
  7. 代码行13,addTestsToSuite,好熟悉的名字啊,原来就是第一个宏里面的函数嘛,原来这里就是它调用的地方啊
  8. 代码行14,release了,返回测试包的指针,智能指针清空了
  9. 代码行17,基本上这就是占位子用的,名字起得也很清楚,这是为了让编译器检查写宏的时候别忘了写那个‘;’,这个技巧倒是挺好玩的

    总结一下,这个宏只有一个函数suite,用于创建测试包,这个函数不仅仅是addTestsToSuite函数的结束,同时也是addTestsToSuite调用的地方。

总结

    宏CPPUNIT_TEST_SUITE,CPPUNIT_TEST,CPPUNIT_TEST_SUITE_END,这三个宏 彼此配合,形成了以下模式:

CPPUNIT_TEST_SUITE( 类 );
CPPUNIT_TEST( 成员函数 );
CPPUNIT_TEST( 成员函数 );
CPPUNIT_TEST( 成员函数 );
。。。
CPPUNIT_TEST_SUITE_END();

    其中注册了自定义的测试类信息,注册了所有自定义的测试函数信息,最后通过一个suite函数创建了一个测试包的对象。

遗留问题

    上一节,我们初步了解了CppUnit通过一组宏注册自定义测试类和自定义测试函数,并创建测试包对象的全过程。但是其中还留下了一些遗留问题,本节将彻底为你揭开剩下的谜团。

构建测试包

    在构建测试包的过程中,有一个类起到了关键的作用,这就是TestSuiteBuilderContext,这是一个模板类,从TestSuiteBuilderContextBase派生,查看了这两个类的声明后,我们得到了下面的类图:

    居于核心位置的TestSuiteBuilderContextBase同时聚合了TestSuite和TestFixtureFactory两个类的对象,实际上它更象是一个中间联络人,让我们看一下它的函数实现:

void TestSuiteBuilderContextBase::addTest( Test *test )
{
    m_suite.addTest( test );
}

TestFixture 
* TestSuiteBuilderContextBase::makeTestFixture() const
{
    
return m_factory.makeFixture();
}

    都是直接转交给相应的类对象。

    TestSuiteBuilderContextBase中还有一个成员变量值得注意:

typedef std::pair<std::string,std::string> Property;
typedef CppUnitVector
<Property> Properties;

private:
Properties m_properties;

    很明显,这里用字符串的形式记录了测试包创建过程中的所有属性。

    我们再来看子类:

template<class Fixture>
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函数的实现是直接转交给相应工厂的。

    那么我们再看看这个工厂,都是很简单的代码:

/*! \brief Abstract TestFixture factory (Implementation).
 *
 * 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关联起来。让我们看看它的函数实现:

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

 
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函数的实现,用函数指针的语法,一次执行一个自定义测试函数。但是这个函数是如何被调用的呢?这话说起来就长了,我们留给下回分解吧。

posted @ 2009-02-03 23:15  oowgsoo  阅读(314)  评论(0编辑  收藏  举报