CSDN博客

img thesecondwoodstock

CppUnit源码解读(2)

发表于2004/3/8 11:28:00  905人阅读

【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/,moyingzz@etang.com

基本测试相关类


在CppUnit中,有一个贯穿始终的最基本的pattern,那便是Composite Pattern。在GoF中对该pattern有如下描述:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。在CppUnit的框架中,测试类分为两种,某些测试类代表单个测试,比如稍后讲到的TestCase(测试用例),另一些则由若干测试类共同构成,比如稍后讲到的TestSuite(测试包)。彼此相关的TestCase共同构成一个TestSuite,而TestSuite也可以嵌套包含。两者分别对应Composite Pattern中的Leaf和Composite。

[Test]

相关文件:Test.h

这是所有测试类的抽象基类,规定了所有测试类都应该具有的行为,对应于Composite Pattern中的Component,除了标准的virtual dtor外,还定义了四个纯虚函数:

    // 运行测试内容,并利用传入其内的TestResult搜集测试结果,类似Component的Operation操作
    virtual void run (TestResult *result) = 0;

    // 返回当前包含的测试对象的个数,若为TestCase,则返回1。
    virtual int countTestCases () const = 0;

    // 返回测试的名称,每个测试都有一个名称,就像是标识,用以查找或显示
    virtual std::string getName () const = 0;

    // 本测试的简短描述,用于调试输出。
    // 对测试的描述除了名称外,可能还有其他信息,
    // 比如:一个名为“complex_add”的测试包可能被描述成“suite complex_add”
    virtual std::string toString () const = 0;

[TestFixture]

相关文件:TestFixture.h

该类也是抽象类,用于包装测试类使之具有setUp方法和tearDown方法。利用它,可以为一组相关的测试提供运行所需的公用环境(即所谓的fixture)。要实现这一目的,你需要:

此外,作为完整的测试类,还要定义一些执行具体测试任务的测试方法,然后使用TestCaller进行测试。关于TestCaller,在helper部分将会讲到。

因为每个测试对象运行在其自身的fixture中,所以测试对象之间不会有副作用(side effects),而测试对象内部的测试方法则共同使用同一个fixture。

来看一下TestFixture的定义,除了标准的virtual dtor外,还定义了两个纯虚函数:

// 在运行测试之前设置其上下文,即fixture
// 一般而言setUp更为重要些,除非实例变量创建于heap中,否则其资源的回收就无需手工处理了
virtual void setUp() {};

// 在测试运行结束之后进行资源回收
virtual void tearDown() {};

[TestCase]

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

派生自Test和TestFixture(多重继承),兼具两者特性,用于实现一个简单的测试用例。你所要做的就是派生该类,并重载runTest方法。不过通常你不必如此,而是使用TestCaller结合TestFixture的方法,这样很方便。当你发现TestCaller无法满足,你需要重写一个功能近似的类时,再使用TestCase也不迟。关于TestCaller,在helper部分将会讲到。

TestCase中最重要的方法是run方法,来看一下代码,并请留意morning的注释:

void TestCase::run( TestResult *result )
{
  // 不必关心startTest的具体行为,在讲到TestResult时自然会明白
  // 末尾的endTest亦是如此
  result->startTest(this);

  try {
    // 设置fixture,具体内容需留待派生类解决
    // 可能有异常抛出,处理方式见后
    setUp();

    // runTest具有protected属性,是真正执行测试的函数
    // 但具体行为需留待派生类解决
    try {
      runTest();
    }
    // 在运行测试时可能会抛出异常,以下是异常处理
    catch ( Exception &e ) {
      // Prototype  Pattern的一个应用
      // e是临时对象,addFailure调用之后即被销毁,所以需要创建一个副本
      Exception *copy = e.clone();
      result->addFailure( this, copy );
    }
    catch ( std::exception &e ) {
      // 异常处理的常用方法——转意
      result->addError( this, new Exception( e.what() ) );
    }
    catch (...) {
      // 截获其余未知异常,一网打尽
      Exception *e = new Exception( "caught unknown exception" );
      result->addError( this, e );
    }

    // 资源回收
    try {
      tearDown();
    }
    catch (...) {
      result->addError( this, new Exception( "tearDown() failed" ) );
    }
  } 
  catch (...) {
    result->addError( this, new Exception( "setUp() failed" ) );
  }

  result->endTest( this );
}

可以看到,run方法定义了一个测试类运行的基本行为及其顺序:

  • setUp:准备
  • runTest:开始
  • tearDown:结束

而TestCase作为抽象类无法确定测试的具体行为,因此需要留待派生类解决,这就是Template Method Pattern。事实上,该pattern在framework中是很常见的。因此一个完整测试的简单执行方法是,从TestCase派生一个类,重载相关方法,并直接调用run方法(正如TestFixture中所提到的)。

有意思的是,TestCase中还有run的另一个版本,它没有形参,而是创建一个缺省的TestResult,然后调用前述run方法。不过好像没怎么用到,大概是先前调试时未及清理的垃圾代码,也难怪会有“FIXME: what is this for?”这样的注释了。

TestCase有两个ctor:

TestCase( std::string Name );        // 测试类的名称
TestCase();

后者主要用于TestCaller,因为在使用TestCaller时,需要一个default ctor[奇怪,编译器按理可以自动生成default ctor,这么做岂不画蛇添足,将该函数去掉,照样可以运行]

此外,TestCase将copy ctor和operator=声明为private属性,以防止误用。

[TestSuite]

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

一组相互关联的测试用例,构成了一个测试包,这就是TestSuite,也就是Composite Pattern中的Composite。和TestCase一样,也派生自Test,只是没有fixture特性。除了测试类的名称外,在TestSuite中还维护了一个测试对象数组,它被声明为private属性:

std::vector<Test *> m_tests;   // [可否使用vector<Test &>呢?]
const std::string m_name;

来看一下TestSuite的run方法是如何实现的,并请留意morning的注释:

void TestSuite::run( TestResult *result )
{
  // 遍历vector<Test *>
  for ( std::vector<Test *>::iterator it = m_tests.begin();
        it != m_tests.end();
        ++it )
  {
    // 可能中途终止
    if ( result->shouldStop() )
      break;

    Test *test = *it;
    // 调用每个test自己的run
    // 可能是TestCase实例,也可能是TestSuite实例,
    // 后者形成递归,但此处却全然不知
    test->run( result );
  }
}

关于TestResult及其shouldStop方法,稍后会讲到。不过此处的break,到也算是活用Composite Pattern的一个简单范例。从效率的角度考虑,当确信不必再执行后续的test时,即可直接返回,而不是照葫芦画瓢,简单的调用一下test的run方法。

既然TestResult派生自Test,那么countTestCases又是如何实现的呢:

int TestSuite::countTestCases() const
{
  int count = 0;

  // 遍历vector<Test *>
  for ( std::vector<Test *>::const_iterator it = m_tests.begin();
        it != m_tests.end();
        ++it )
    count += (*it)->countTestCases();   // 递归调用每个test的countTestCases,并累加
  return count;
}

至于addTest,自然是不能少的,它对应于Composite的Add方法:

void TestSuite::addTest( Test *test )
{
  m_tests.push_back( test );   // 将test添加到测试对象数组的尾端
}

不过请注意,addTest方法并未出现于抽象类Test中,关于这类设计上的权衡在GoF中,Composite Pattern一节有专门的论述。

TestSuite管理着其下所属诸测试对象的生命周期,在dtor中,它会调用deleteContents方法:

void TestSuite::deleteContents()
{
  for ( std::vector<Test *>::iterator it = m_tests.begin();
        it != m_tests.end();
        ++it)
    delete *it;
  m_tests.clear();
}

此外,TestSuite还为外部访问其所属测试对象提供了接口,因为返回值是const &类型的,所以是read-only的:

const std::vector<Test *> &getTests() const;
0 0

相关博文

我的热门文章

img
取 消
img