CppUnit源码解读(4) ---错误处理

本文深入剖析了CppUnit框架中的断言机制,包括TestFailure类的定义与使用,以及NotEqualException异常处理方式。详细介绍了如何通过SourceLine类记录错误发生的源文件位置和行号。
摘要由CSDN通过智能技术生成

[TestFailure] [SourceLine] [Exception] [NotEqualException]  

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

CppUnit中有两种类型的错误,它们分别是:failure和error。一个failure是可预期的,并可以为断言(assert)所侦测到;而error则是不可预期的,由异常标示,它并非框架代码所产生。

CppUnit使用TestFailure这一个类同时表示failure和error,请看TestFailure的成员变量定义:

protected:
  // 指向失败的测试对象
  Test *m_failedTest;
  // 指向异常对象(如果有)
  Exception *m_thrownException;
  // 区分failure和error的标记
  bool m_isError;

再来看相关代码:

// 依据failedTest和thrownException,构建一个TestFailure
TestFailure::TestFailure( Test *failedTest, 
        Exception *thrownException,
        bool isError ) :
    m_failedTest( failedTest ), 
    m_thrownException( thrownException ),
    m_isError( isError )
{
}

// 返回m_isError,判断是否error的函数
bool TestFailure::isError() const
{
  return m_isError;
}

另外,再来看看有关clone的代码,十分简单:

TestFailure *TestFailure::clone() const
{
  // 创建一个和自身一模一样的实例后返回其指针
  return new TestFailure( m_failedTest, m_thrownException->clone(), m_isError );
}

除了m_isError之外,TestFailure也为另两个成员变量提供了外界访问的接口:

std::string TestFailure::failedTestName() const
{
  return m_failedTest->getName();
}

Exception *TestFailure::thrownException() const
{
  return m_thrownException;
}

最后,对于执行失败的测试,TestFailure还记录了其错误所在位置,包括源文件路径和文件内的行号,通过如下接口可以访问到:

SourceLine TestFailure::sourceLine() const
{
  return m_thrownException->sourceLine();
}

可以看到,TestFailure调用了Exception的sourceLine方法。为使外部方便的引用SourceLine,选择增加自身接口(sourceLine方法),而接口实现仅仅是简单的delegate,这种设计权衡在很多地方都是经常用到的。关于SourceLine的具体实现,随后就会讲到。

 

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

记录了有关某个失败测试的错误所在位置,包括源文件路径和文件内的行号。有了它,我们就可以准确定位导致测试失败的原因了。在和Asserter相关的一些宏中将会用到该类,Exception中也引用了该类。

SourceLine中有两个成员变量,分别对应源文件路径和文件内的行号:

private:
  std::string m_fileName;
  int m_lineNumber;

除了为这两个成员变量提供外界访问的接口外,SourceLine还重载了operator==和operator!=:

int SourceLine::lineNumber() const
{
  return m_lineNumber;
}

std::string SourceLine::fileName() const
{
  return m_fileName;
}

bool SourceLine::operator ==( const SourceLine &other ) const
{
  return m_fileName == other.m_fileName  &&
          m_lineNumber == other.m_lineNumber;
}

bool SourceLine::operator !=( const SourceLine &other ) const
{
  // 调用operator==,即保证了语义的正确,又避免了代码重复
  // 此类做法是库设计中经常用到的,在STL源码中随处可见
  return !( *this == other );
}

可以看出,SourceLine只是简单的包装了错误位置这一信息,至于该信息的设定还需外界决定,那么如何才能记录下源文件中错误所在的位置呢,SourceLine中还有一个宏,全部的秘密都在这里:

#define CPPUNIT_SOURCELINE() ::CppUnit::SourceLine( __FILE__, __LINE__ )

在需要处调用该宏,比如某个断言,一个SourceLine对象即被构造,m_fileName和m_lineNumber便被初始化为宏展开处的位置信息。在讲到TestAssert时,你将会看到,这些工作都无需你操心了,因为framework已经安排好一切了。

 

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

这就是前面多次提到过的异常。它派生自std::exception,调用其what方法可以得到有关本次异常的描述信息,在某个断言失败时会抛出异常。

Exception中有一个标识异常类型的inner class——Type,它具有public属性,内部仅有一个常量成员变量m_Type,代表具体类型。唯一的ctor在初始化成员列表中为m_type赋值,此外还有一个operator==函数:

class Type
{
public:
  Type( std::string type ) : m_type ( type ) {}
  
  bool operator ==( const Type &other ) const
  {
    return m_type == other.m_type;
  }
  
private:
  const std::string m_type;
};

将类型信息用类来封装,应该是典型的OO风格了,这在refactoring一书中被称为“Replace Type Code with Class”技法,另外,读者还可以在该书中找到其它几项相关内容:Replace Conditional with Polymorphism,Replace Type Code with Subclasses,Replace Type Code with State/Strategy,Replace Data Value with Object。

与Type相关的两个函数是type和isInstanceOf:

// 返回Exception的类型
Exception::Type Exception::type()
{
  return Type( "CppUnit::Exception" );
}

// 判断exceptionType是否是“CppUnit::Exception”
bool Exception::isInstanceOf( const Type &exceptionType ) const
{
  return exceptionType == type();
}

isInstanceOf实现了一个简单的运行期类型检查,类似于MFC中的IsKindOf。在Exception的派生类中还将见到它。

Exception有两个私有成员变量:

private:
  std::string m_message;     // 与本次异常相关的描述信息
  SourceLine m_sourceLine;   // 异常发生的具体位置

查看Exception的ctor发现有两个不同版本:

Exception( std::string  message = "", 
        SourceLine sourceLine = SourceLine() );

#ifdef CPPUNIT_ENABLE_SOURCELINE_DEPRECATED
Exception( std::string  message, long lineNumber, std::string fileName );
#endif

关于这个CPPUNIT_ENABLE_SOURCELINE_DEPRECATED的来历,在随CppUnit所附的ChangeLog中有过“记载”。早先版本的CppUnit中,Exception并未引用SourceLine类(也就是说没有第一个ctor,当时SourceLine还没“出世”呢),而是代之以fileName和lineNumber这两个成员变量,这一点从第二个ctor的声明中也能看出来。在随后的一次refactoring过程中,这两个可怜的家伙被“Introduce Parameter Object”消灭掉了,于是SourceLine取而代之。但是,作为一个发布了的framework,需要考虑到兼容问题,因此以前的接口必须保留,所以第二个ctor仍然存在,只是其内部实现已偷梁换柱了:

Exception::Exception( std::string message, long lineNumber, std::string fileName ) : 
    m_message( message ), 
    m_sourceLine( fileName, lineNumber )  // 仍然转交给SourceLine
{}

既然是“DEPRECATED”,那么这样的接口当然是不推荐使用的,在Exception源码中还有多处与此有关,这里就不多说了。总之,缺省情况下CPPUNIT_ENABLE_SOURCELINE_DEPRECATED没有被定义,因此被#ifdef CPPUNIT_ENABLE_SOURCELINE_DEPRECATED……#endif所包围的代码大可不必关心。不过,像这类“历史遗留”问题,想必很多库设计中都会遇到。

还有几个函数,代码如下:

const char* Exception::what() const throw()
{
  return m_message.c_str ();
}

Exception *Exception::clone() const
{
  return new Exception( *this );
}

Exception::operator =( const Exception& other )
{
  if ( &other != this )
  {
    m_message = other.m_message;
    m_sourceLine = other.m_sourceLine;
  }
  return *this;
}

 

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

派生自Exception,当判断相等的断言失败时会抛出该异常。和基类相比,多了三个private成员变量:

private:
  std::string m_expected;    // 预期值
  std::string m_actual;          // 实际值,是否仅支持字符串比较呢,稍后再做讲解
  std::string m_additionalMessage;     // 附加信息

NotEqualException还覆盖了基类的type、isInstanceOf、clone、operator=这几个函数:

// 返回NotEqualException类型
Exception::Type NotEqualException::type()
{
  return Type( "CppUnit::NotEqualException" );
}

// 调用了基类的isInstanceOf,因此若传入的值为Exception的Type,
// 返回亦为true,这一点与常理相符
bool NotEqualException::isInstanceOf( const Type &exceptionType ) const
{
  return exceptionType == type()  ||
          Exception::isInstanceOf( exceptionType );
}

Exception *NotEqualException::clone() const
{
  return new NotEqualException( *this );
}

NotEqualException &
NotEqualException::operator =( const NotEqualException &other )
{
  Exception::operator =( other );     // 切勿忘记调用基类的operator=
  
  if ( &other != this )
  {
    m_expected = other.m_expected;
    m_actual = other.m_actual;
    m_additionalMessage = other.m_additionalMessage;
  }
  return *this;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值