【声明】如需复制、传播,请附上本声明,谢谢。原文出处:http://morningspace.51.net/,moyingzz@etang.com
错误处理
[TestFailure] 相关文件: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,构建一个TestFailureTestFailure::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] 相关文件: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] 相关文件: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_DEPRECATEDException( 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] 相关文件: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;}