【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/,moyingzz@etang.com
扩展部分(Extension)
在CppUnit中,除了提供基本的单元测试之外,还增加了很多扩展测试,比如:重复测试(RepeatedTest),正规测试(OrthodoxTest),这些内容都悉数收录在extension中。
[TestDecorator] 相关文件:TestDecorator.h 它提供了一种方法,可以不用子类化Test类,同时又能扩展Test类的功能。我们可以派生TestDecorator,并用它来包装Test。其实这种方法是Decorator Pattern的一个应用,在GoF中对该pattern有如下描述:动态地给一个对象添加一些额外的职责。就增加功能来说,比生成子类更为灵活。 TestDecorator维护了一个指向Test实例的指针,并在ctor中设定。不过该实例的生命期,TestDecorator并不过问: protected: Test *m_test;随后是四个public函数,其接口与Test的接口完全一致: void run (TestResult *result);int countTestCases () const;std::string getName () const;std::string toString () const;函数的实现就是简单的调用m_test的对应接口: inline int TestDecorator::countTestCases () const{ return m_test->countTestCases (); }inline void TestDecorator::run (TestResult *result){ m_test->run (result); }inline std::string TestDecorator::toString () const{ return m_test->toString (); }inline std::string TestDecorator::getName () const{ return m_test->getName(); }在TestDecorator的派生类中,这些功能将得到扩展。 [RepeatedTest] 相关文件:RepeatedTest.h,RepeatedTest.cpp 派生自TestDecorator,其功能是对测试重复运行指定的次数(类似于某种强度测试)。private成员变量m_timesRepeat记录了重复的次数: private: const int m_timesRepeat;该值在ctor中设定: RepeatedTest( Test *test,int timesRepeat ) : TestDecorator( test ), m_timesRepeat(timesRepeat) {}这里的test参数,就是所要执行的测试,可能是某个测试用例,也可能是测试包。 随后是函数countTestCases、run和toString的子类化版本: // 返回本次测试中测试用例的总数// 总数 = 实际总数 * 重复次数int RepeatedTest::countTestCases() const{ return TestDecorator::countTestCases () * m_timesRepeat;}std::string RepeatedTest::toString() const{ return TestDecorator::toString () + " (repeated)";}// 运行测试// 重复调用基类的run,并在基类中调用m_test的run方法void RepeatedTest::run( TestResult *result ){ for ( int n = 0; n < m_timesRepeat; n++ ) { if ( result->shouldStop() ) break; TestDecorator::run( result ); }}[Orthodox] 相关文件:Orthodox.h 该类实现了正规测试的功能。它派生自TestCase,是一个模板类,有一个类型参数ClassUnderTest,代表将要运行的测试。所谓正规测试,就是对待测类(即ClassUnderTest)执行一组简单的测试,确保其至少具有如下基本操作: default ctor operator==和operator!= assignment(即operator=) operator! safe passage(即copy ctor) 若其中任何一项没有通过测试,则模板类就不会实例化。否则,实例化后将检查这些操作的语义是否正确。当你需要确认一组待测类具有相同表现时,采用被模板化的测试用例非常有用,Orthodox就是一个很好的例子。可以想见,在实际工作中,我们也可以效仿Orthodox的做法,从而“扩展”CppUnit以适应自己的特定环境。 以下代码演示了如何将一个复数类的正规测试添加到测试包中: TestSuite *suiteOfTests = new TestSuite;suiteOfTests->addTest (new ComplexNumberTest ("testAdd");suiteOfTests->addTest (new TestCaller<Orthodox<Complex> > ()); // 非常简单来看一下Orthodox的定义: template <typename ClassUnderTest> class Orthodox : public TestCase{public: Orthodox () : TestCase ("Orthodox") {}protected: ClassUnderTest call (ClassUnderTest object); void runTest ();};唯一需要解释的就是runTest方法,Orthodox是如何检查ClassUnderTest是否符合要求的呢: template <typename ClassUnderTest>void Orthodox<ClassUnderTest>::runTest (){ // 确保default ctor被定义,否则无法通过编译 ClassUnderTest a, b, c; // 确保operator==被定义,否则无法通过编译 // 同时检查operator==的语义 CPPUNIT_ASSERT (a == b); // 确保operator!、operator=和operator!=被定义 // 否则无法通过编译 // 同时检查operator!=的语义 b.operator= (a.operator! ()); CPPUNIT_ASSERT (a != b); // 检查operator!和operator==的语义 b = !!a; CPPUNIT_ASSERT (a == b); b = !a; // 以下检查copy ctor是否被定义及其语义正确与否 c = a; CPPUNIT_ASSERT (c == call (a)); c = b; CPPUNIT_ASSERT (c == call (b));}这里的call是辅助函数,“迫使”编译器调用copy ctor,以检查safe passage: template <typename ClassUnderTest>ClassUnderTest Orthodox<ClassUnderTest>::call (ClassUnderTest object){ return object;}所有的奥妙就在上面这几行代码中。 [TestSetUp] 相关文件:TestSetUp.h,TestSetUp.cpp 同样派生自TestDecorator,它使测试类具有了SetUp和TearDown的特性。关于这两个特性请见core部分的TestFixture。 该类定义了两个protected属性的虚函数,以供派生类覆盖: protected: virtual void setUp(); virtual void tearDown();此外,就是子类化了run方法: void TestSetUp::run( TestResult *result ){ setUp(); TestDecorator::run(result); tearDown();}
[后记]
正如我在“CppUnit源码解读(1)”一文中所说,这一系列的文章是从《CppUnit源码解读》中摘选出来的,并且我认为是主要部分的章节。 我打算将本文做为这一系列文章的结尾,不过,对此仍感兴趣的朋友将可以在我的主页上找到这一系列文章的完整版本:http://morningspace.51.net/resource/cppunit/cppunit_anno.html。
当然,所谓主要也只是个人的主观划分。比如CppUnit中另一个重要环节——结果输出,这里就没有提到,那是Observer Pattern的用武之地。另外,CppUnit的类厂实现机制也是很有特色的。就这些议题,你将可以在完整版中找到。
这么做,是出于如下的考虑:文章的内容毕竟出于源码的个人阅读笔记(最初是写给自己看的:),并且由于时间关系,我也无法将其做得十分精致(比如就像有人抱怨的没有画上图示,类图等等:)。看久了难免让人觉得有点像记流水帐,让人觉得乏味。与其如此,到不如让那些不介意流水账的朋友来网站看后续内容,而介意者也就无需再继续看下去了:)
再次重申,本文系源码笔记,如果你想了解有关CppUnit的使用,请参考其他文献,据我所知CSDN的文档中心里就有一篇入门性的文章:)
—— Morning