zoukankan      html  css  js  c++  java
  • 构建测试包

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

  • 相关阅读:
    struts2-20-下载文件及授权控制
    struts2-19-合法用户上传文件
    struts2-18-上传多文件
    struts2-17-上传单个文件
    struts2-16-userAnnotationValidate
    struts2-15-用户名校验
    struts2-14-用户自定义全局转换器
    struts2-13-用户自定义局部转换器
    struts2-12-用户自定义转换器(地址)
    struts2-11-OGNL实现书籍的增删改查
  • 原文地址:https://www.cnblogs.com/oowgsoo/p/1383486.html
Copyright © 2011-2022 走看看