核心部分(Core)——测试结果记录


从这里开始,将要讲述core中,测试结果记录的相关部分。

CppUnit是支持多线程的,你可以在一个线程中执行测试,在另一个线程中收集测试结果;或者在不同线程中并行执行多个测试,而用一个线程收集测试结果。framework中为此提供了简单而必要的支持。

[SynchronizedObject]

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

SynchronizedObject用来管理一个被同步对象,前面提到的TestResult就是从该类派生的。所谓被同步对象,是指其成员会被多个线程并发使用。

SynchronizedObject定义了一个public属性的abstract inner class——SynchronizationObject,代表具备同步属性的对象:

class SynchronizationObject
{
  public:
    SynchronizationObject() {}
    virtual ~SynchronizationObject() {}
    virtual void lock() {}
    virtual void unlock() {}
};

此类定义了互斥锁功能,但具体行为需在其派生类中实现。不同环境下的实现方式想必也不尽相同。随CppUnit源码所附的范例中有个MfcSynchronizationObject就是SynchronizationObject的子类,它使用了MFC的CCriticalSection:

class MfcSynchronizationObject
    : public CppUnit::SynchronizedObject::SynchronizationObject
{
  CCriticalSection m_syncObject;
public:
  void lock()
  {
    m_syncObject.Lock();
  }
  
  void unlock()
  {
    m_syncObject.Unlock();
  }
};

SynchronizedObject还定义了一个protected属性的inner class——ExclusiveZone,作为内部使用的辅助类。它用于在当前作用域内锁定一个SynchronizationObject的实例。其实现类似于std::auto_ptr,它持有一个指向SynchronizationObject对象的指针,ctor中调用lock,dtor中调用unlock:

class ExclusiveZone
{
  SynchronizationObject *m_syncObject;
  
public:
  ExclusiveZone( SynchronizationObject *syncObject )
      : m_syncObject( syncObject )
  {
    m_syncObject->lock();
  }
  
  ~ExclusiveZone()
  {
    m_syncObject->unlock ();
  }
};

除去这些,SynchronizedObject就很简单了。它持有一个指向SynchronizationObject实例的指针:

SynchronizationObject *m_syncObject;

并管理其生命周期,在dtor中delete之。至于如何传入该指针,则提供了两种方法:

SynchronizedObject::SynchronizedObject( SynchronizationObject *syncObject )
    : m_syncObject( syncObject == 0 ? new SynchronizationObject() : 
                                                                  syncObject )
{
}

void SynchronizedObject::setSynchronizationObject( SynchronizationObject *syncObject )
{
  delete m_syncObject; 
  m_syncObject = syncObject;
}

在讲述TestResult之前,还有一些障碍要扫清。

[TestListener]

相关文件:TestListener.h

CppUnit的测试结果记录使用了Observer Pattern,在GoF中对该pattern有如下描述:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在测试执行的过程中,当发生错误时,TestResult就会被告知,而后它会将该信息传递给TestListener。因此,TestResult对应了Observer Pattern中的Subject,而TestListener则对应了Observer。

不过TestListener只是一个什么事都没做的基类,像输出测试结果这样事情还得留待派生类来解决,比如outputter部分要提到的TextTestProgressListener。此外,还是尽量不要使用Listener进行测试结果的输出为好,应该使用outputter中所提供的专有工具。

从TestListener的定义可以看到,在如下三类事件发生时,TestListener将会被通知到

来看一下代码:

// 在一个测试运行前被调用
virtual void startTest( Test *test ) {}

// 运行测试失败时被调用
virtual void addFailure( const TestFailure &failure )

// 在一个测试运行后被调用
virtual void endTest( Test *test )

addFailure中的failure是临时对象,在该方法调用之后即被销毁,和Exception一样(参见TestCase的run函数),也需要使用其自身提供的clone方法来创建一个副本。有关TestFailure,稍后将会提到。

至于endTest,即便测试失败,该函数也会被调用,参见TestCase的run函数。

[TestResult]

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

真是千呼万唤始出来啊。TestResult用以收集测试过程中的相关信息,它派生自SynchronizedObject,从而支持多线程。有了前面的铺垫,对TestResult的理解就变得非常容易了。

TestResult维护了一个指向TestListener对象的指针队列:

protected:
  typedef std::deque<TestListener *> TestListeners;
  TestListeners m_listeners;

为获取到测试相关信息,TestListener需要注册到TestResult中,于是就有了addListener方法:

void TestResult::addListener( TestListener *listener )
{
  ExclusiveZone zone( m_syncObject );     // ExclusiveZone终于有用武之地了
  m_listeners.push_back( listener );
}

当然还少不了removeListener:

void TestResult::removeListener ( TestListener *listener )
{
  ExclusiveZone zone( m_syncObject );
  m_listeners.erase( std::remove( m_listeners.begin(), 
                        m_listeners.end(), 
                        listener ),
                     m_listeners.end());
}

我们再来看看TestResult作为Subject的那些Notify方法:

void TestResult::addError( Test *test, Exception *e )
{
  addFailure( TestFailure( test, e, true ) );
}

void TestResult::addFailure( Test *test, Exception *e )
{
  addFailure( TestFailure( test, e, false ) );
}

void TestResult::addFailure( const TestFailure &failure )
{
  ExclusiveZone zone( m_syncObject );
  // 遍历deque<TestListener *>
  for ( TestListeners::iterator it = m_listeners.begin();
      it != m_listeners.end();
      ++it )
    (*it)->addFailure( failure );   // 调用TestListener的addFailure
}

void TestResult::startTest( Test *test )
{
  ExclusiveZone zone( m_syncObject );
  // 遍历deque<TestListener *>
  for ( TestListeners::iterator it = m_listeners.begin();
      it != m_listeners.end(); 
      ++it )
    (*it)->startTest( test );   // 调用TestListener的startTest
}

void TestResult::endTest( Test *test )
{
  ExclusiveZone zone( m_syncObject );
  // 遍历deque<TestListener *>
  for ( TestListeners::iterator it = m_listeners.begin();
      it != m_listeners.end();
      ++it )
    (*it)->endTest( test );   // 调用TestListener的endTest
}

此处的注释足以说明问题,至于error和failure的区别,在讲到TestFailure时自然会明了。

最后再来看看有关shouldStop的代码,该函数曾在TestSuite的run中出现过:

TestResult::TestResult( SynchronizationObject *syncObject )
    : SynchronizedObject( syncObject )
{
  reset();
}

void TestResult::reset()
{
  ExclusiveZone zone( m_syncObject );
  m_stop = false;
}

bool TestResult::shouldStop() const
{
  ExclusiveZone zone( m_syncObject );
  return m_stop;
}

void TestResult::stop()
{
  ExclusiveZone zone( m_syncObject );
  m_stop = true;
}

没有什么特别的,只是一个m_stop在掌控着一切,而m_stop则是TestResult的一个protected属性的成员变量:

bool m_stop;

返回目录