[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; }