【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/,moyingzz@etang.com
错误处理
相关文件:TestFailure.h,TestFailure.cpp
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方法可以得到有关本次异常的描述信息,在某个断言失败时会抛出异常。
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;
}
本文围绕CppUnit展开,介绍了其中两种错误类型failure和error,以及用TestFailure类表示它们。还阐述了SourceLine类记录错误位置,Exception类处理异常,包括其类型封装、版本兼容等问题,最后介绍了派生自Exception的NotEqualException类。
1045

被折叠的 条评论
为什么被折叠?



