发布时间:2010-2-12 11:56
分类名称:Private
这里我们再次看到了wasDestroyed和needDestroy,正如前面所述,它们是为了防止多次销毁同一个 TestFactoryRegistry实例的。对于如下的代码:
registerFactory( "All Tests", getRegistry( "Unit Tests" ) );
在了解了TestFactoryRegistry::getRegistry的实际行为之后,你会发现,名为 “Unit Tests”的注册项将同时被名为”All Tests”的注册项和NamedRegistries所拥有。此外,morning以为,对于没有在NamedRegistries中注册的 TestFactoryRegistry实例,调用needDestroy的结果同样为true,此处也保证可以被及时销毁,而不致造成内存泄漏。由此足 见,此处的注册机制是相当灵活的。
上面出现的m_factories是TestFactoryRegistry的一个private属性的成员变量:
Factories m_factories; // 代表一个类工厂名称-类工厂实例的映射表
Factories的定义如下:
typedef std::map<std::string, TestFactory *> Factories;
至于getRegistry方法,则有两个版本,一个有形参,另一个则没有:
TestFactoryRegistry &
TestFactoryRegistry::getRegistry()
{
// 调用另一个版本的getRegistry,并传入“All Tests”
// 一般代表某组测试的“根节点”
return getRegistry( "All Tests" );
}
TestFactoryRegistry &
TestFactoryRegistry::getRegistry( const std::string &name )
{
// 获取NamedRegistries的实例,并调用其getRegistry方法
return NamedRegistries::getInstance().getRegistry( name );
}
前面提到,某个类工厂的注册项,其下可能包含一组类工厂,即形成所谓的层状结构。为了支持这一功能,TestFactoryRegistry提供了 registerFactory方法,同样有两个不同版本:
// 给定类工厂的名称及其对应的实例指针
void
TestFactoryRegistry::registerFactory( const std::string &name,
TestFactory *factory )
{
m_factories[name] = factory;
}
// 只给出了类工厂的实例指针,此之渭Unnamed TestFactory
void
TestFactoryRegistry::registerFactory( TestFactory *factory )
{
// 通过serialNumber自动形成名称,再调用另一个版本的registerFactory
// static变量serialNumber从1开始,每次累加1
static int serialNumber = 1;
OStringStream ost;
ost << "@Dummy@" << serialNumber++;
registerFactory( ost.str(), factory );
}
当层状结构构建好后,就可以调用makeTest方法,创建待运行的测试实例了,请留意morning的注释:
Test *
TestFactoryRegistry::makeTest()
{
// 创建一个测试包,并冠以m_name的名称
TestSuite *suite = new TestSuite( m_name );
// 调用addTestToSuite,以将其下所属的测试实例添加到suite中
addTestToSuite( suite );
// 返回测试包对应的指针
return suite;
}
void
TestFactoryRegistry::addTestToSuite( TestSuite *suite )
{
// 遍历其下所属的各个TestFactory实例
for ( Factories::iterator it = m_factories.begin();
it != m_factories.end();
++it )
{
TestFactory *factory = (*it).second;
// 调用factory的makeTest方法创建测试实例
// 将指向实例的指针添加到suite中
// makeTest的调用可能形成连锁反应
suite->addTest( factory->makeTest() );
}
// 当所有的factory都遍历完后,即所有的测试实例都被创建成功后
// 整个测试实例的层状结构也就构建成功了
}
以下对CppUnit的注册机制作一个简单的小结:
以下提供几个使用TestFactoryRegistry类的例子,以加深认识:
// 例1:创建一个空的测试包,并将与之对应的类工厂注册项注册到NamedRegistries中
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
CppUnit::TestSuite *suite = registry.makeTest();
// 例2:创建一个名为“Math”的测试包,并将与之对应的类工厂注册项注册到NamedRegistries中
CppUnit::TestFactoryRegistry &mathRegistry = CppUnit::TestFactoryRegistry::getRegistry( "Math" );
CppUnit::TestSuite *mathSuite = mathRegistry.makeTest();
// 例3:创建一个名为“All tests”的测试包,并将名为“Graph”和“Math”的测试包作为“All tests”测试包的子项
// 与全部三个测试包对应的类工厂注册项都被注册到NamedRegistries中
CppUnit::TestSuite *rootSuite = new CppUnit::TestSuite( "All tests" );
rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Graph" ).makeTest() );
rootSuite->addTest( CppUnit::TestFactoryRegistry::getRegistry( "Math" ).makeTest() );
CppUnit::TestFactoryRegistry::getRegistry().addTestToSuite( rootSuite );
// 例4:例3的另一中实现方式
CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
registry.registerFactory( CppUnit::TestFactoryRegistry::getRegistry( "Graph" ) );
registry.registerFactory( CppUnit::TestFactoryRegistry::getRegistry( "Math" ) );
CppUnit::TestSuite *suite = registry.makeTest();
相关文件:TestSuiteFactory.h
模板类,派生自TestFactory,是TestFixture的类工厂,并且该TestFixture必须实现一个静态的suite方法,以便在覆盖 TestFactory的makeTest时调用:
// 此处的TestCaseType就是一个TestFixture
template<typename TestCaseType>
class TestSuiteFactory : public TestFactory
{
public:
virtual Test *makeTest()
{
return TestCaseType::suite();
}
};
相关文件:TestSuiteBuilder.h
模板类,用以将一系列测试添加到一个测试包中。所有加入该测试包的测试,其固有名称之前都会被加上测试包的名称,形成类似如下的测试名 称:MyTestSuiteName.myTestName,前者为测试包的名称,后者为测试本身的名称。
TestSuiteBuilder内部维护了一个m_suite指针以指向对应的测试包实例,这是一个Smart Pointer,因此其生命周期无需手工操控,而是由TestSuiteBuilder来维护:
std::auto_ptr<TestSuite> m_suite;
至于m_suite所指的对象,可以由TestSuiteBuilder自己创建,也可以从外面传入,全凭你选择调用ctor的哪个版本了:
// 使用type_info生成测试包的名称
#if CPPUNIT_USE_TYPEINFO_NAME
TestSuiteBuilder() :
m_suite( new TestSuite(
TypeInfoHelper::getClassName( typeid(Fixture) ) ) )
{
}
#endif
TestSuiteBuilder( TestSuite *suite ) : m_suite( suite )
{
}
TestSuiteBuilder(std::string name) : m_suite( new TestSuite(name) )
{
}
添加测试的方法是简单地调用m_suite的addTest:
void addTest( Test *test )
{
m_suite->addTest( test );
}
此外,为了方便使用,TestSuiteBuilder还提供了几个用于添加TestCaller的方法,它们调用 makeTestName以生成测试名称,最终都将调用addTest。其中,Fixture是TestSuiteBuilder的模板类型参数, TestMethod的定义如下:
typedef void (Fixture::*TestMethod)();
至于TestCaller,稍后会讲到:
void addTestCaller(std::string methodName,
TestMethod testMethod )
{
Test *test =
new TestCaller<Fixture>( makeTestName( methodName ),
testMethod );
addTest( test );
}
void addTestCaller(std::string methodName,
TestMethod testMethod,
Fixture *fixture )
{
Test *test =
new TestCaller<Fixture>( makeTestName( methodName ),
testMethod,
fixture);
addTest( test );
}
template<typename ExceptionType>
void addTestCallerForException(std::string methodName,
TestMethod testMethod,
Fixture *fixture,
ExceptionType *dummyPointer )
// dummyPointer本身没有实际作用,此处只为获取其所属类型
{
Test *test = new TestCaller<Fixture,ExceptionType>(
makeTestName( methodName ),
testMethod,
fixture);
addTest( test );
}
makeTestName的定义如下:
std::string makeTestName( const std::string &methodName )
{
return m_suite->getName() + "." + methodName;
}
为了便于外界访问m_suite指针,TestSuiteBuilder还提供了如下辅助方法:
TestSuite *suite() const
{
return m_suite.get();
}
TestSuite *takeSuite()
{
return m_suite.release();
}
相关文件:TestCaller.h
在前面以及core部分曾经多次提到TestCaller,此类的作用是根据一个fixture创建一个测试用例。当你需要单独运行某个测试,或者要将其 添加到某个测试包中时,你就可以使用TestCaller。一个TestCaller仅对应一个 Test类,该Test类和一个TestFixture相关联。下面是一个演示的例子:
// 一个TestFixture,并包含了test method(s)
class MathTest : public CppUnit::TestFixture {
...
public:
void setUp();
void tearDown();
void testAdd();
void testSubtract();
};
CppUnit::Test *MathTest::suite() {
CppUnit::TestSuite *suite = new CppUnit::TestSuite;
// 将MathTest::testAdd加入TestCaller,并将该TestCaller加入测试包中
suite->addTest( new CppUnit::TestCaller<MathTest>( "testAdd", testAdd ) );
return suite;
}
你可是使用TestCaller,将任意一个test方法和某个TestFixture绑定在一起,只要该test方法满足如下形式的定义:
void testMethod(void);
TestCaller其实是一个模板类,它派生自TestCase,有两个模板类型参数,前一个参数代表了 TestFixture类,后一个参数代表某个异常类,缺省类型为NoExceptionExpected,至于该参数的作用,稍后便知分晓。关于 TestCase,请见core部分:
template <typename Fixture,
typename ExpectedException = NoExceptionExpected>
class TestCaller : public TestCase
{
//...
};
TestCaller有三个private属性的成员变量:
Fixture *m_fixture; // 指向TestFixture实例的指针
bool m_ownFixture; // 若为true,则由TestCaller负责维护m_fixture的生命周期
TestMethod m_test; // 指向某个test方法的函数指针
TestMethod的定义如下:
typedef void (Fixture::*TestMethod)();
正是因为有如上定义,才限制了TestCaller只能支持形参和返回值均为为void类型的test方法。
来看一下TestCaller的ctor和dtor,你会发现,对于Fixture的安置工作,CppUnit的实现者可谓细心周到:
// 由TestCaller创建fixture,负责维护其生命周期
TestCaller( std::string name, TestMethod test ) :
TestCase( name ),
m_ownFixture( true ),
m_fixture( new Fixture() ),
m_test( test )
{
}
// 由外界传入fixture,TestCaller不负责维护其生命周期
TestCaller( std::string name, TestMethod test, Fixture& fixture) :
TestCase( name ),
m_ownFixture( false ),
m_fixture( &fixture ),
m_test( test )
{
}
// 由外界传入fixture,由TestCaller负责维护其生命周期
TestCaller( std::string name, TestMethod test, Fixture* fixture) :
TestCase( name ),
m_ownFixture( true ),
m_fixture( fixture ),
m_test( test )
{
}
// 根据m_ownFixture,决定是否销毁m_fixture
~TestCaller()
{
if (m_ownFixture)
delete m_fixture;
}
作为TestCaller的基类,TestCase派生自Test和TestFixture,因此TestCaller有义务实现如下三个虚函数。
void setUp()
{
// 简单地调用了m_fixture的setUp方法
m_fixture->setUp ();
}
void tearDown()
{
// 简单地调用了m_fixture的tearDown方法
m_fixture->tearDown ();
}
void runTest()
{
// 调用了m_fixture的一个test方法
// 由此可见:
// - test方法必须在Fixture类中定义
// - 若Fixture类中存在多个test方法,则需一一建立与之对应的TestCaller
try {
(m_fixture->*m_test)();
}
catch ( ExpectedException & ) {
return;
}
ExpectedExceptionTraits<ExpectedException>::expectedException();
}
这里不得不提到辅助类ExpectedExceptionTraits,还有前面出现过的NoExceptionExpected,它们和 TestCaller一起被定以在同一个文件里。
template<typename ExceptionType>
struct ExpectedExceptionTraits
{
static void expectedException()
{
#if CPPUNIT_USE_TYPEINFO_NAME
std::string message( "Expected exception of type " );
message += TypeInfoHelper::getClassName( typeid( ExceptionType ) );
message += ", but got none";
#else
std::string message( "Expected exception but got none" );
#endif
throw Exception( message );
}
};
ExpectedExceptionTraits只有一个static方法,其唯一的作用是抛出一个 Exception类型的异常,并附带一个说明信息,指出某个预计产生的异常并未出现,该预计的异常由ExpectedExceptionTraits的 模板类型参数来指定。关于Exception,请见core部分。结合前面出现过的TestCaller::runTest的行为,我们可以得出如下结 论:
通常情况下,如果调用m_fixture的test方法时,没有抛出任何异常,或者抛出的不是 ExpectedException类型的异常,则ExpectedExceptionTraits的expectedException方法会产生一个 异常来指出这一错误。其中的ExpectedException,由TestCaller的第二个类型参数来指定。
再看一下NoExceptionExpected的定义,它被作为TestCaller的第二个类型参数的缺省类型:
class CPPUNIT_API NoExceptionExpected
{
private:
// 防止此类被实例化
NoExceptionExpected();
};
什么事都不做!是的,NoExceptionExpected只是一个Marker class,它用来表明,TestCaller在任何时候都不会对运行test方法时所抛出的异常做预期性检查。当然,光有了 NoExceptionExpected还不够,还需要定义一个ExpectedExceptionTraits的特化版本:
template<>
struct ExpectedExceptionTraits<NoExceptionExpected>
{
static void expectedException()
{
}
};
同样是什么事都没做,再次结合TestCaller::runTest的行为,我们就可以得出如下结论:
当TestCaller的第二个类型参数为NoExceptionExpected时,如果调用m_fixture的test方法时
相关文件:AutoRegisterSuite.h
AutoRegisterSuite是一个模板类,其作用是自动注册指定类型的测试包,不过你不需要直接使用该类,而代之以如下的两个宏:
CPPUNIT_TEST_SUITE_REGISTRATION()
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()
关于宏,在随后的HelperMacros部分将会有更为详细的说明。
AutoRegisterSuite的全部内容是两个不同版本的ctor:
AutoRegisterSuite()
{
// 利用TestSuiteFactory创建一个类工厂实例factory
TestFactory *factory = new TestSuiteFactory<TestCaseType>();
// 调用getRegistry(),在NamedRegistries中注册一个TestFactoryRegistry实例,
// 注册项的名称为“All Tests”,并调用该实例的registerFactory,将factory注册为
// 其下所属的一个类工厂实例
TestFactoryRegistry::getRegistry().registerFactory( factory );
}
AutoRegisterSuite( const std::string &name )
{
// 除了注册项的名称由外部指定外,其余同前
TestFactory *factory = new TestSuiteFactory<TestCaseType>();
TestFactoryRegistry::getRegistry( name ).registerFactory( factory );
}
这里的TestCaseType是AutoRegisterSuite的模板类型参数。前面曾经提到过的 TestSuiteFactory,其makeTest方法会调用TestCaseType的suite方法以创建测试实例,至于makeTest的调用 时机,则和TestFactoryRegistry的makeTest方法有关。
相关文件:HelperMacros.h
这里定义了一系列宏,它们为简化CppUnit的使用提供了诸多便利。其中,宏CPPUNIT_TEST_SUITE(), 宏CPPUNIT_TEST()和宏CPPUNIT_TEST_SUITE_END()被设计用来简化创建测试包的过程。比如:
class MyTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE( MyTest );
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST( testSetName );
CPPUNIT_TEST_SUITE_END();
public:
void testEquality();
void testSetName();
};
这些宏在MyTest内部定义了两个方法,第一个方法是一个名为registerTests的辅助函数,你无需直接调用。第二个方法的定义如下:
static CppUnit::TestSuite *suite();
该方法返回一个指向测试包实例的指针,该测试包对应的测试实例就是由CPPUNIT_TEST()指定的。比起手工调用suite(),使用 CPPUNIT_TEST_SUITE_REGISTRATION()更为方便,它会创建一个static属性的 AutoRegisterSuite类型变量,该变量将自动把测试包实例注册到NamedRegistries中。NamedRegistries中包含 了所有被注册的测试包实例:
CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );
CppUnit::Test* tp =
CppUnit::TestFactoryRegistry::getRegistry().makeTest();
上述宏还支持带模板类型参数的测试类,比如:
template<typename CharType>
class StringTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE( StringTest );
CPPUNIT_TEST( testAppend );
CPPUNIT_TEST_SUITE_END();
public:
...
};
然后,可以在StringTest类的实现代码(即.cpp文件)中加上如下代码:
CPPUNIT_TEST_SUITE_REGISTRATION( StringTest<char> );
CPPUNIT_TEST_SUITE_REGISTRATION( StringTest<wchar_t> );
下面为你逐一讲解各个宏的实现机理。同时为了便于理解,我以前面的MyTest为例,分别给出宏展开前后的代码,并在结尾处给出一个小结。首先是 CPPUNIT_TEST_SUITE()的定义:
#define CPPUNIT_TEST_SUITE( ATestFixtureType ) \
private: \
typedef ATestFixtureType __ThisTestFixtureType; \
class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory \
{ \
virtual CppUnit::TestFixture *makeFixture() \
{ \
return new ATestFixtureType(); \
} \
}; \
public: \
static void \
registerTests( CppUnit::TestSuite *suite, \
CppUnit::TestFixtureFactory *factory ) \
{ \
CppUnit::TestSuiteBuilder<__ThisTestFixtureType> builder( suite );
该宏表明开始定义一个新的测试包,它必须出现在其他宏的前面。以MyTest为例,宏展开前的代码如下:
CPPUNIT_TEST_SUITE( MyTest );
展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:
private:
class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory
{
virtual CppUnit::TestFixture *makeFixture()
{
return new MyTest();
}
};
public:
static void
registerTests( CppUnit::TestSuite *suite,
CppUnit::TestFixtureFactory *factory )
{
CppUnit::TestSuiteBuilder<MyTest> builder( suite );
正如前面提到的,public部分定义了一个static属性的registerTests方法,稍后讲到 CPPUNIT_TEST_SUITE_END()时即会看到其作用。此外,这里还出现了一个inner class:ThisTestFixtureFactory,它派生自TestFixtureFactory。至于 TestFixtureFactory,有如下定义:
class TestFixtureFactory
{
public:
// 创建一个新的TestFixture实例
virtual CppUnit::TestFixture *makeFixture() =0;
};
可见ThisTestFixtureFactory只是覆盖了TestFixtureFactory的纯虚函数 makeFixture,创建了一个MyTest的实例。至于makeFixture的调用时机,则是在CPPUNIT_TEST()处。以下是 CPPUNIT_TEST()的定义:
#define CPPUNIT_TEST( testMethod ) \
builder.addTestCaller( #testMethod, \
&__ThisTestFixtureType::testMethod , \
(__ThisTestFixtureType*)factory->makeFixture() )
该宏用于每次向测试包添加一个测试方法,测试方法必须满足形参和返回值都为void类型。以MyTest的testEquality为例,宏展开前的代码 如下:
CPPUNIT_TEST( testEquality );
展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:
builder.addTestCaller( "testEquality",
&MyTest::testEquality,
(MyTest*)factory->makeFixture() )
这里调用了TestSuiteBuilder的addTestCaller,在TestSuiteBuilder内部所维护的m_suite中添加了一个 测试实例,确切地说是一个TestCaller,该TestCaller是专门用来测试testEquality方法的,这也是为什么测试方法必须满足形 参和返回值都为void类型的原因。关于TestSuiteBuilder和TestCaller,前面已经有过叙述了。
再来看一下CPPUNIT_TEST_SUITE_END()的定义:
#define CPPUNIT_TEST_SUITE_END() \
builder.takeSuite(); \
} \
static CppUnit::TestSuite *suite() \
{ \
CppUnit::TestSuiteBuilder<__ThisTestFixtureType> \
builder __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType ); \
ThisTestFixtureFactory factory; \
__ThisTestFixtureType::registerTests( builder.suite(), &factory ); \
return builder.takeSuite(); \
} \
private: /* 此处的typedef并无实际作用,只是为了使该宏可以以分号结束 */ \
typedef ThisTestFixtureFactory __ThisTestFixtureFactory
该宏表明一个测试包定义的结束,因为末尾那个无关痛痒的typdef,其后的成员变量隐式情况下都将具有private属性。 __CPPUNIT_SUITE_CTOR_ARGS()宏稍后会提到,此处并不影响理解,以MyTest为例,宏展开前的代码如下:
CPPUNIT_TEST_SUITE_END();
展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:
builder.takeSuite();
}
static CppUnit::TestSuite *suite()
{
CppUnit::TestSuiteBuilder<MyTest> builder;
ThisTestFixtureFactory factory;
MyTest::registerTests( builder.suite(), &factory );
return builder.takeSuite();
}
这里出现了前面曾经提到的static属性的suite方法,它在构建了TestSuiteBuilder和 ThisTestFixtureFactory之后,便开始调用前面提到的那个registerTests方法,其结果是向builder内含的 m_suite中加入若干TestCaller实例。最后,它将返回指向该m_suite实例的指针。
还有一个需要提到的关键的宏是CPPUNIT_TEST_SUITE_REGISTRATION(),其定义如下:
#define CPPUNIT_TEST_SUITE_REGISTRATION( ATestFixtureType ) \
static CppUnit::AutoRegisterSuite< ATestFixtureType > \
__CPPUNIT_MAKE_UNIQUE_NAME(__autoRegisterSuite )
该宏声明了一个static属性的全局变量,该变量将自动把测试包实例注册到NamedRegistries中。 __CPPUNIT_MAKE_UNIQUE_NAME()宏稍后会提到,此处并不影响理解,以MyTest为例,宏展开前的代码如下:
CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );
展开后的代码如下,此处略去或修改了部分与主体关系不大的代码:
// __autoRegisterSuite123是一个变量名,后面的123代表了当前代码行号,
// 至于该变量名是如何产生的,__CPPUNIT_MAKE_UNIQUE_NAME部分将会提到
static CppUnit::AutoRegisterSuite< MyTest > __autoRegisterSuite123
以下就上面的内容给出一个小结。类MyTest在宏展开前的代码大致如下:
// MyTest.h
class MyTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE( MyTest );
CPPUNIT_TEST( testEquality );
CPPUNIT_TEST_SUITE_END();
public:
void testEquality();
};
// MyTest.cpp
CPPUNIT_TEST_SUITE_REGISTRATION( MyTest );
展开后的代码如下:
// MyTest.h
class MyTest : public CppUnit::TestFixture {
private:
class ThisTestFixtureFactory : public CppUnit::TestFixtureFactory
{
virtual CppUnit::TestFixture *makeFixture()
{
return new MyTest();
}
};
public:
static void
registerTests( CppUnit::TestSuite *suite,
CppUnit::TestFixtureFactory *factory )
{
CppUnit::TestSuiteBuilder<MyTest> builder( suite );
builder.addTestCaller( "testEquality",
&MyTest::testEquality,
(MyTest*)factory->makeFixture() )
builder.takeSuite();
}
static CppUnit::TestSuite *suite()
{
CppUnit::TestSuiteBuilder<MyTest> builder;
ThisTestFixtureFactory factory;
MyTest::registerTests( builder.suite(), &factory );
return builder.takeSuite();
}
public:
void testEquality();
};
// MyTest.cpp
static CppUnit::AutoRegisterSuite< MyTest > __autoRegisterSuite123
并在适当的地方,添加如下代码:
CppUnit::TestFactoryRegistry ®istry =
CppUnit::TestFactoryRegistry::getRegistry(); // 对应“All Tests”的注册项
registry.makeTest();
由此得到测试用例创建的完整流程如下:
1. 定义AutoRegisterSuite<MyTest>类型的static全局变量,在其ctor中完成向NamedRegistries的注册,即在NamedRegistries中注册一个名为“All Tests”的注册项,并在其下注册一个TestSuiteFactory<MyTest>类型的实例(但该实例并未放在 NamedRegistries中)
2. 取得NamedRegistries中的对应“All Tests”的注册项,并调用makeTest方法
* 2.1 创建一个TestSuite的实例
* 2.2 得到那个TestSuiteFactory<MyTest>类型的实例,并调用其makeTest方法,以得到一个真正的测试用例
* 2.3 将测试用例添加到先前定义的那个TestSuite实例中(通过调用其addTest方法实现)
关于2.2,有进一步的说明:
TestSuiteFactory<MyTest>的makeTest方法事实上调用了MyTest的suite方法,而该suite方法正 是前面运用CPPUNIT_TEST_SUITE_END()所产生的,进入suite之后,流程如下:
1. 创建TestSuiteBuilder<MyTest>类型和ThisTestFixtureFactory类型的变量builder、factory
2. 将builder内含的测试包m_suite和factory作为实参,调用registerTests方法
3. 调用TestSuiteBuilder<MyTest>的addTestCaller,创建一个TestCaller实例,该实例对应一个测试方法(比如:本例中的testEquality)
至此,整个测试用例的创建过程圆满结束。并且,如果使用多个CPPUINT_TEST宏即可注册多个测试方法,每个测试方法都有一个TestCaller 的实例与之对应。
再来看看HelperMacros中的其他几个宏。与CPPUNIT_TEST_SUITE()类似的另一个宏是 CPPUNIT_TEST_SUB_SUITE(),它同样是用于表明开始定义一个新的测试包,不过它仅在如下场合使用:基类中已经通过使用 CPPUNIT_TEST_SUITE()或CPPUNIT_TEST_SUB_SUITE()定义了测试包。其定义如下:
#define CPPUNIT_TEST_SUB_SUITE( ATestFixtureType, ASuperClass ) \
private: \
typedef ASuperClass __ThisSuperClassType; \
CPPUNIT_TEST_SUITE( ATestFixtureType ); \
__ThisSuperClassType::registerTests( suite, factory )
此处首先调用了CPPUNIT_TEST_SUITE(),随后是调用基类的registerTests方法,如此一来,基类中定义的所有 TestCaller都将被自动添加到当前派生类中所定义的这个测试包中,即最后的结果是:当前定义的测试包中包含了基类和派生类中出现的所有测试方法。 来看一个使用CPPUNIT_TEST_SUB_SUITE()的例子:
class MySubTest : public MyTest {
CPPUNIT_TEST_SUB_SUITE( MySubTest, MyTest );
CPPUNIT_TEST( testAdd );
CPPUNIT_TEST( testSub );
CPPUNIT_TEST_SUITE_END();
public:
void testAdd();
void testSub();
};
与CPPUNIT_TEST()类似的还有另两个宏,它们分别是CPPUNIT_TEST_EXCEPTION()和 CPPUNIT_TEST_FAIL()。CPPUNIT_TEST_EXCEPTION()也用于向测试包添加一个测试方法,与 CPPUNIT_TEST()不同的是,它指定了一个预期会抛出的异常,若该异常成功产生,则一切照旧,否则便会导致测试失败。其定义如下:
#define CPPUNIT_TEST_EXCEPTION( testMethod, ExceptionType ) \
builder.addTestCallerForException( #testMethod, \
&__ThisTestFixtureType::testMethod , \
(__ThisTestFixtureType*)factory->makeFixture(), \
(ExceptionType *)NULL );
来看一个使用CPPUNIT_TEST_EXCEPTION()的例子:
#include <vector>
class MyTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE( MyTest );
CPPUNIT_TEST_EXCEPTION( testVectorAtThrow, std::invalid_argument );
CPPUNIT_TEST_SUITE_END();
public:
void testVectorAtThrow()
{
std::vector<int> v;
v.at( 1 ); // 必将抛出std::invalid_argument异常
}
};
CPPUNIT_TEST_FAIL()与CPPUNIT_TEST_EXCEPTION()很相似,其定义如下:
#define CPPUNIT_TEST_FAIL( testMethod ) \
CPPUNIT_TEST_EXCEPTION( testMethod, CppUnit::Exception )
该宏用于向测试包添加一个测试方法,并预期其必定会失败,事实上就是预期其必定会抛出CppUnit:: Exception异常。通常,如果在测试用例中用到了与断言相关的宏时,就可以使用该宏了,关于“与断言相关的宏”请见core部分。来看一个使用 CPPUNIT_TEST_FAIL()的例子:
CPPUNIT_TEST_FAIL( testAssertFalseFail );
void testAssertFalseFail()
{
CPPUNIT_ASSERT( false );
}
与CPPUNIT_TEST_SUITE_REGISTRATION()类似的另一个宏是 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()。它们都会创建一个AutoRegisterSuite类型的 static全局变量,并在其ctor中,将一个新创建的测试包的类工厂实例注册到NamedRegistries中。所不同的是, CPPUNIT_TEST_SUITE_REGISTRATION()没有指定注册项的名字,代之以缺省的“All Tests”,而CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()则通过第二个参数指定了注册项的名字。其定义如下:
#define CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ATestFixtureType, suiteName ) \
static CppUnit::AutoRegisterSuite< ATestFixtureType > \
__CPPUNIT_MAKE_UNIQUE_NAME(__autoRegisterSuite )(suiteName)
来看一个使用CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()的例子:
// MySuites.h
namespace MySuites {
std::string math() {
return "Math";
}
}
// ComplexNumberTest.cpp
#include "MySuites.h"
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( ComplexNumberTest, MySuites::math() );
上面的例子中使用了一个static方法返回测试包的名称,比起直接在代码中引入代表测试包名称的字符串(也就是所谓的Hardcoded String),这样的方式更为灵活。它能使你免于因为拼写错误而无法获得正确的注册项名称。
最后,稍带提一下几个次要的宏。在CPPUNIT_TEST_SUITE()中定义 TestSuiteBuilder实例时曾经用到过__CPPUNIT_SUITE_CTOR_ARGS()。该宏在 CPPUNIT_USE_TYPEINFO_NAME被定义的时候(即你所使用的编译器支持RTTI机制),不起任何作用;否则,将返回一个字符串。至于 该字符串的作用,请见TestSuiteBuilder部分的讲解。
#if CPPUNIT_USE_TYPEINFO_NAME
# define __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType )
#else
# define __CPPUNIT_SUITE_CTOR_ARGS( ATestFixtureType ) (std::string(#ATestFixtureType))
#endif
在CPPUNIT_TEST_SUITE_REGISTRATION()和 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()中曾经用到过__CPPUNIT_MAKE_UNIQUE_NAME ()。该宏的作用是产生一个唯一的全局性名字,它有一个参数str,将str和__LINE__“粘合”在一起便是最终的结果。其中,__LINE__是 一个隐含的静态变量,代表了当前代码在文件中所处的行号。
#define __CPPUNIT_CONCATENATE_DIRECT( s1, s2 ) s1##s2
#define __CPPUNIT_CONCATENATE( s1, s2 ) __CPPUNIT_CONCATENATE_DIRECT( s1, s2 )
#define __CPPUNIT_MAKE_UNIQUE_NAME( str ) __CPPUNIT_CONCATENATE( str, __LINE__ )
在CppUnit中,除了提供基本的单元测试之外,还增加了很多扩展测试,比如:重复测试(RepeatedTest),正规测试 (OrthodoxTest),这些内容都悉数收录在extension中。
相关文件:TestDecorator.h
它提供了一种方法,可以不用子类化Test类,同时又能扩展Test类的功能。我们可以派生TestDecorator,并用它来包装Test。其实这种 方法是Decorator Pattern的一个应用,在GoF中对该pattern有如下描述:动态地给一个对象添加一些额外的职责。就增加功能来说,比生成子类更为灵活。
TestDecorator维护了一个指向Test实例的指针,并在ctor中设定。不过该实例的生命期,TestDecorator并不过问:
protected:
Test *m_test;
随后是四个public函数,其接口与Test的接口完全一致:
void run (TestResult *result);
int countTestCases () const;
std::string getName () const;
std::string toString () const;
函数的实现就是简单的调用m_test的对应接口:
inline int TestDecorator::countTestCases () const
{ return m_test->countTestCases (); }
inline void TestDecorator::run (TestResult *result)
{ m_test->run (result); }
inline std::string TestDecorator::toString () const
{ return m_test->toString (); }
inline std::string TestDecorator::getName () const
{ return m_test->getName(); }
在TestDecorator的派生类中,这些功能将得到扩展。
相关文件:RepeatedTest.h,RepeatedTest.cpp
派生自TestDecorator,其功能是对测试重复运行指定的次数(类似于某种强度测试)。private成员变量m_timesRepeat记录了 重复的次数:
private:
const int m_timesRepeat;
该值在ctor中设定:
RepeatedTest( Test *test,int timesRepeat ) :
TestDecorator( test ),
m_timesRepeat(timesRepeat) {}
这里的test参数,就是所要执行的测试,可能是某个测试用例,也可能是测试包。
随后是函数countTestCases、run和toString的子类化版本:
// 返回本次测试中测试用例的总数
// 总数 = 实际总数 * 重复次数
int RepeatedTest::countTestCases() const
{
return TestDecorator::countTestCases () * m_timesRepeat;
}
std::string RepeatedTest::toString() const
{
return TestDecorator::toString () + " (repeated)";
}
// 运行测试
// 重复调用基类的run,并在基类中调用m_test的run方法
void RepeatedTest::run( TestResult *result )
{
for ( int n = 0; n < m_timesRepeat; n++ )
{
if ( result->shouldStop() )
break;
TestDecorator::run( result );
}
}
相关文件:Orthodox.h
该类实现了正规测试的功能。它派生自TestCase,是一个模板类,有一个类型参数ClassUnderTest,代表将要运行的测试。所谓正规测试, 就是对待测类(即ClassUnderTest)执行一组简单的测试,确保其至少具有如下基本操作:
若其中任何一项没有通过测试,则模板类就不会实例化。否则,实例化后将检查这些操作的语义是否正确。当你需要确认一组待测类具有相同表现时,采用被模板化 的测试用例非常有用,Orthodox就是一个很好的例子。可以想见,在实际工作中,我们也可以效仿Orthodox的做法,从而“扩展”CppUnit 以适应自己的特定环境。
以下代码演示了如何将一个复数类的正规测试添加到测试包中:
TestSuite *suiteOfTests = new TestSuite;
suiteOfTests->addTest (new ComplexNumberTest ("testAdd");
suiteOfTests->addTest (new TestCaller<Orthodox<Complex> > ()); // 非常简单
来看一下Orthodox的定义:
template <typename ClassUnderTest> class Orthodox : public TestCase
{
public:
Orthodox () : TestCase ("Orthodox") {}
protected:
ClassUnderTest call (ClassUnderTest object);
void runTest ();
};
唯一需要解释的就是runTest方法,Orthodox是如何检查ClassUnderTest是否符合要求的呢:
template <typename ClassUnderTest>
void Orthodox<ClassUnderTest>::runTest ()
{
// 确保default ctor被定义,否则无法通过编译
ClassUnderTest a, b, c;
// 确保operator==被定义,否则无法通过编译
// 同时检查operator==的语义
CPPUNIT_ASSERT (a == b);
// 确保operator!、operator=和operator!=被定义
// 否则无法通过编译
// 同时检查operator!=的语义
b.operator= (a.operator! ());
CPPUNIT_ASSERT (a != b);
// 检查operator!和operator==的语义
b = !!a;
CPPUNIT_ASSERT (a == b);
b = !a;
// 以下检查copy ctor是否被定义及其语义正确与否
c = a;
CPPUNIT_ASSERT (c == call (a));
c = b;
CPPUNIT_ASSERT (c == call (b));
}
这里的call是辅助函数,“迫使”编译器调用copy ctor,以检查safe passage:
template <typename ClassUnderTest>
ClassUnderTest Orthodox<ClassUnderTest>::call (ClassUnderTest object)
{
return object;
}
所有的奥妙就在上面这几行代码中。
相关文件:TestSetUp.h,TestSetUp.cpp
同样派生自TestDecorator,它使测试类具有了SetUp和TearDown的特性。关于这两个特性请见core部分的 TestFixture。
该类定义了两个protected属性的虚函数,以供派生类覆盖:
protected:
virtual void setUp();
virtual void tearDown();
此外,就是子类化了run方法:
void TestSetUp::run( TestResult *result )
{
setUp();
TestDecorator::run(result);
tearDown();
}
这部分较为简单,主要根据具体需求,提供了两个TestListener的派生类,它们分别用在不同的场合。
相关文件:TestSucessListener.h,TestSucessListener.cpp
派生自TestListener和SynchronizedObject(多重继承),兼具两者特性。作为一个实际的Observer,接收来自 TestResult的信息,用以“监听”测试是否成功。关于TestListener、 SynchronizedObject以及TestResult请见core部分的说明。
TestSucessListener内部所持有的成员变量m_sucess标示了测试成功与否,至于究竟如何“监听”,不妨来看一下 TestSucessListener的相关实现:
TestSucessListener::TestSucessListener( SynchronizationObject *syncObject )
: SynchronizedObject( syncObject )
, m_sucess( true )
{
}
在ctor中,m_sucess被初始化为true。至于syncObject,则提供了同步功能,为后续调用ExclusiveZone提供便利。
void TestSucessListener::addFailure( const TestFailure &failure )
{
ExclusiveZone zone( m_syncObject );
m_sucess = false;
}
在测试发生错误时,TestResult将会调用TestSucessListener的addFailure,后者只是简单地将m_sucess设置为 false。TestSucessListener只关心测试成功与否,至于有关测试结果的详细情况则由 TestResultCollector负责“监听”,关于TestResultCollector请见output部分。因为要考虑多线程环境,所以用 到了ExclusiveZone,这也是为什么TestSucessListener需要继承SynchronizedObject的原因(别忘了 ExclusiveZone是protected属性的)。m_syncObject就是前面ctor中提到的syncObject。
void TestSucessListener::reset()
{
ExclusiveZone zone( m_syncObject );
m_sucess = true;
}
顾名思义,在测试运行之前reset内部状态,将m_sucess置为true。
最后,为外部提供测试成功与否的查询接口也是必不可少的:
bool TestSucessListener::wasSuccessful() const
{
ExclusiveZone zone( m_syncObject ); // [此处是read操作,似不必劳驾ExclusiveZone]
return m_sucess;
}
相关文件:TextTestProgressListener.h,TextTestProgressListener.cpp
派生自TestListener,用来“监听”测试用例的运行状态,并将结果定向到标准错误输出设备(即屏幕)。作为一个简易的文本流方式的结果输出工 具,TextTestProgressListener已是绰绰有余了。
// 继承自基类的虚函数,在测试运行前被调用
void TextTestProgressListener::startTest( Test *test )
{
std::cerr << ".";
std::cerr.flush();
}
// 继承自基类的虚函数,运行测试失败时被调用
void TextTestProgressListener::addFailure( const TestFailure &failure )
{
std::cerr << ( failure.isError() ? "E" : "F" );
std::cerr.flush();
}
// 在测试运行后被调用[疑为endTest,也许是作者的疏忽]
void TextTestProgressListener::done()
{
std::cerr << std::endl;
std::cerr.flush();
}
相关文件:TextTestResult.h,TextTestResult.cpp
以文本流方式输出测试运行的结果。不过该类在新版本中已被标上了“DEPRECATED”,并被 TextTestProgressListener和TextOutputter(在outputter部分讲解)所取代。因为是不推荐使用的,所以此处 不准备细述了,感兴趣的读者可以自己看。
这一部分主要提供了一个文本界面的测试运行环境(即以字符流方式输出到标准输出设备)。该测试环境在CppUnit中被称为test runner,对应的类是TestRunner。TestRunner可以运行所有测试,或者是其中的一个。下面的代码演示了如何使用 TestRunner:
CppUnit::TextUi::TestRunner runner;
runner.addTest( ExampleTestCase::suite() );
runner.run( "", true ); // 空字串""代表运行所有的测试
在测试执行期间,TestRunner除了能输出最后的统计结果外,还可以打印输出跟踪信息。其中,跟踪信息的输出使用了 TextTestProgressListener(见listener部分),统计结果的输出则使用了TextOutputter(见 outputter部分)。当然,这些都是可选的。你可以在构造TestRunner期间或者随后通过调用setOutputter函数来指定其他类型的 outputter。你也可以通过在eventManager()中注册其他TestListener,来定制跟踪信息。且看下面的示例:
CppUnit::TextUi::TestRunner runner;
runner.addTest( ExampleTestCase::suite() );
// 用CompilerOutputter代替TextOutputter
runner.setOutputter( CppUnit::CompilerOutputter::defaultOutputter(
&runner.result(), std::cerr ) );
// 添加自定义的MyCustomProgressTestListener
MyCustomProgressTestListener progress;
runner.eventManager().addListener( &progress );
runner.run( "", true ); // 空字串""代表运行所有的测试
最后,TestRunner管理着其下所有测试对象的生命周期。
相关文件:TestRunner.h,TestRunner.cpp,TextTestRunner.h
TestRunner中定义了4个protected属性的成员变量:
TestSuite *m_suite; // 对应待运行的测试
TestResultCollector *m_result; // 搜集测试结果(见output部分)
TestResult *m_eventManager; // 收集测试过程中的相关信息
Outputter *m_outputter; // 输出测试统计结果
对于这几个成员变量的作用,注释中及前面部分已有提及。在这里,大家对TestResult和TestResultCollector的功能可能容易混 淆。对于它们的区别以及TestResultCollector的“身世”,请见output部分。
TestRunner的ctor对这几个成员变量进行了初始化,若外部传入的outputter为空,则缺省创建TextOutputter对象赋给 m_outputter,另外还调用了m_eventManager的addListener方法,将 m_result作为一个兼听者加入其中。
TestRunner::TestRunner( Outputter *outputter )
: m_outputter( outputter )
, m_suite( new TestSuite( "All Tests" ) )
, m_result( new TestResultCollector() )
, m_eventManager( new TestResult() )
{
if ( !m_outputter )
m_outputter = new TextOutputter( m_result, std::cout );
m_eventManager->addListener( m_result );
}
dtor则负责回收在ctor中所创建的资源:
TestRunner::~TestRunner()
{
delete m_eventManager;
delete m_outputter;
delete m_result;
delete m_suite;
}
正如前面所说,你可以在构造TestRunner之后,通过调用setOutputter函数来指定其他类型的outputter:
void TestRunner::setOutputter( Outputter *outputter )
{
delete m_outputter;
m_outputter = outputter;
}
TestRunner中的主要接口就是run方法,通过调用该函数才能运行测试:
bool TestRunner::run( std::string testName,
bool doWait,
bool doPrintResult,
bool doPrintProgress )
{
runTestByName( testName, doPrintProgress );
printResult( doPrintResult );
wait( doWait );
return m_result->wasSuccessful();
}
其中,testName是测试用例的名称,若为空则运行所有测试,否则运行指定测试;doWait为true时,表示在 run函数返回之前,用户必须按一下RETURN键才能结束;doPrintResult为true时,测试结果将被输出到标准输出设备(前提是使用 TextOutputter),否则不产生任何输出;doPrintProgress为true时,测试过程将被输出到标准输出设备(前提是使用 TextTestProgressListener),否则不产生任何输出。
这里调用了runTestByName,请看源码及注释:
bool TestRunner::runTestByName( std::string testName,
bool doPrintProgress )
{
if ( testName.empty() ) // 若testName为空则运行所有测试
return runTest( m_suite, doPrintProgress );
Test *test = findTestByName( testName ); // 否则查找指定测试
if ( test != NULL )
return runTest( test, doPrintProgress ); // 若成功找到则运行之
std::cout << "Test " << testName << " not found." << std::endl;
return false;
}
在runTestByName中,根据测试名称查找指定测试的任务落实到了findTestByName肩上:
Test *TestRunner::findTestByName( std::string name ) const
{
for ( std::vector<Test *>::const_iterator it = m_suite->getTests().begin();
it != m_suite->getTests().end();
++it )
{
Test *test = *it;
if ( test->getName() == name )
return test;
}
return NULL;
}
该函数以const_iterator遍历m_suite中的所有测试,寻找名字相符的测试。不过,从代码中可以看出,这里无法支持多层嵌套结构的测试 集,这也算是一点遗憾吧。
找到指定测试之后,就可以将运行测试的任务转交给runTest方法了:
bool TestRunner::runTest( Test *test, bool doPrintProgress)
{
TextTestProgressListener progress;
if ( doPrintProgress ) // 若doPrintProgress为true则显示测试过程
m_eventManager->addListener( &progress );
test->run( m_eventManager ); // 此处才真正运行测试,m_eventManager就是TestResult
if ( doPrintProgress ) // 若doPrintProgress为true则移除先前加入的progress
m_eventManager->removeListener( &progress );
return m_result->wasSuccessful(); // 返回测试结果成功与否
}
测试结束后,就可以通过调用printResult输出测试结果了:
void TestRunner::printResult( bool doPrintResult )
{
std::cout << std::endl;
if ( doPrintResult )
m_outputter->write();
}
另外,TestRunner还提供了一个addTest方法,用以添加测试,其内部只是简单的调用了一下m_suite的addTest方法,相当简单:
void TestRunner::addTest( Test *test )
{
if ( test != NULL )
m_suite->addTest( test );
}
当然还少不了几个成员变量的getter方法:
TestResultCollector &TestRunner::result() const
{
return *m_result;
}
TestResult &TestRunner::eventManager() const
{
return *m_eventManager;
}
另一个相关的头文件TextTestRunner.h中,仅仅简单地做了一个typedef定义:
typedef CppUnit::TextUi::TestRunner TextTestRunner;
这就是TestRunner的大致内容。
这一部分,通过若干参数的设定,解决了向不同平台移植时遇到的问题。另外还有一个叫做OStringStream的辅助类,不过morning以为,该类 似乎置于helper部分更为合适。
相关文件:Portablility.h
其实OStringStream在先前很多地方都曾经出现过,比如: TestFactoryRegistry中、XmlOutputter中、TestAssert中。其作用是将整数转换为字符串并输出,功能上类似于C语 言的itoa函数。事实上,从随CppUnit所附的ChangeLog中可以看到,先前正是用的itoa,只不过后来的一次refactoring中, 才被OStringStream取代。其实现代码如下:
#if CPPUNIT_HAVE_SSTREAM
# include <sstream>
namespace CppUnit {
class OStringStream : public std::ostringstream
{
};
}
#else
#if CPPUNIT_HAVE_CLASS_STRSTREAM
# include <string>
# if CPPUNIT_HAVE_STRSTREAM
# include <strstream>
# else
# include <strstream.h>
# endif
namespace CppUnit {
class OStringStream : public std::ostrstream
{
public:
std::string str()
{
(*this) << '\0';
std::string msg(std::ostrstream::str());
std::ostrstream::freeze(false);
return msg;
}
};
}
#else
# error Cannot define CppUnit::OStringStream.
#endif
#endif
相关文件:Portability.h,config-msvc6.h,config-bcb5.h
如前所述,CppUnit提供了一系列可供设置的参数(其实就是一系列宏定义),针对不同平台,你需要做出不同的选择。好在CppUnit的实现者为我们 做了很多工作,使我们不用太多考虑这方面的问题,至少在Visual C++ 6.0和Borland C++ Builder 5.0这两个平台上是如此。Portability.h的开头有如下这样一段代码,它依据实际的语言平台(由特定的宏来指定),载入相应的参数设置文件:
#if defined(__BORLANDC__)
# include <cppunit/config-bcb5.h>
#elif defined (_MSC_VER)
# include <cppunit/config-msvc6.h>
#else
# include <cppunit/config-auto.h>
#endif
这里的config-auto.h文件,morning并未在CppUnit的源码中找到,也许是程序作者的一时疏忽。至于在config-xxx.h中 出现的那些参数,此处只列举一二,感兴趣的读者可自己去查看源码。
// helper部分的TypeInfoHelper中曾经出现过该宏
// 它表明在bcb和vc中函数std::string::compare的调用方式不一样
// config-msvc6.h
#ifdef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST
#undef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST
#endif
// config-bcb5.h
#ifndef CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST
#define CPPUNIT_FUNC_STRING_COMPARE_STRING_FIRST 1
#endif
// 指明编译器是否支持c++的namespace语言特性
// bcb和vc均支持namespace
// config-msvc6.h
#ifndef CPPUNIT_HAVE_NAMESPACES
#define CPPUNIT_HAVE_NAMESPACES 1
#endif
// config-bcb5.h
#ifndef CPPUNIT_HAVE_NAMESPACES
#define CPPUNIT_HAVE_NAMESPACES 1
#endif
// OStringStream的定义中曾经出现过,指明是否存在<sstream>头文件,
// bcb和vc均包含有<sstream>头文件
// config-msvc6.h
#define CPPUNIT_HAVE_SSTREAM 1
// config-bcb5.h
#define CPPUNIT_HAVE_SSTREAM 1
此外还有一些,仅在某个平台下才会用到的参数,比如,以下内容均只在config-msvc6.h中出现:
// 忽略Debug符号大于255的警告
#if _MSC_VER > 1000 // VC++
#pragma warning( disable : 4786 )
#endif // _MSC_VER > 1000
// 若当前创建的是CppUnit的DLL库,则需要定义CPPUNIT_DLL_BUILD
#ifdef CPPUNIT_BUILD_DLL
#define CPPUNIT_API __declspec(dllexport)
#endif
// 若当前要链接到CppUnit的DLL库,则需要定义CPPUNIT_DLL
#ifdef CPPUNIT_DLL
#define CPPUNIT_API __declspec(dllimport)
#endif
最后,Portability.h中还定义了一些平台无关的参数,它们设定同样也影响着CppUnit的某些特性,比如:
// 若你希望使用传统风格的断言宏,比如:
// assert(), assertEqual()等等,则需将其设定为1
#ifndef CPPUNIT_ENABLE_NAKED_ASSERT
#define CPPUNIT_ENABLE_NAKED_ASSERT 0
#endif
// CPPUNIT_API是在<config_msvc6.h>中被定以的
// 若没有被定以,则表明程序不需要引用或生成CppUnit的DLL库
#ifndef CPPUNIT_API
#define CPPUNIT_API
#undef CPPUNIT_NEED_DLL_DECL
#define CPPUNIT_NEED_DLL_DECL 0
#endif
目前,CPPUnit在WIN32平台下仅支持Microsoft Visual C++,而且你的VC++编译器至少应该是6.0版本的。
使用GUI TestRunner编译运行示例程序的步骤如下:
1. 在VC++中打开examples/examples.dsw(包含所有的示例)
2. 将HostApp设为active project
3. 编译之
4. 在VC中选择Tools/Customize.../Add-ins and Macro Files,点击Browse...
5. 选择lib/TestRunnerDSPlugIn.dll文件,并按ok以注册该附加件(add-ins)
6. 运行project
框架 & 工具:
所有库文件都被置于lib/目录下。
CppUnit和TestRunner带有3种配置。
对CppUnit而言,当创建dll时,字母“dll” 将被添加到后缀之后。
括号内的字母标明了添加到库名之后的后缀。例如,debug配置的cppunit静态库名为cppunitd.lib。debug配置的cppunit动 态库名为cppunitd_dll.lib.
1. 在VC++中打开src/CppUnitLibraries.dsw工作区文件。
2. 将TestPlugInRunner设为active project。
3. 在'Build'菜单中选择'Batch Build...'
4. 在Batch Build对话框中,选中所有的project 并按下build按钮。
5. 所有的库文件可以在lib/目录下找到。
1. 打开工作区文件examples/Examples.dsw。
2. 将CppUnitTestApp设为active project.
3. 为你要创建的库选择合适的配置。
4. 编译运行project。TestRunner GUI将会出现。
所有编译后生成的库均可在'lib'目录中找到。多数库可以在src/CppUnitLibraries.dsw工作区中创建。
lib\:
注意:当你使用CppUnit DLL(cppunit*_dll.dll)时,你必须连接相关的导入库,并在project中定义预处理标识CPPUNIT_DLL。
为了编写单元测试,你需要连接cppunitXX.lib,此处的XX即所选配置对应的后缀字母。你必须在你的project中打开RTTI开关 (Project Settings/C++/C++ Language)。 CppUnit的include目录必须包含在include查找路径中。你可以通过在Project Settings/C++/Preprocessor/Additional include directories或者Tools/Options/Directories/Include中添加include目录做到这一点。
简言之:
为了使用GUI的test runner,你需要连接testrunnerXX.lib和cppunitXX.lib,此处的XX即所选配置对应的后缀字母。 你必须在你的project中打开RTTI开关。 文件testrunner.dll必须位于你的应用程序所在的路径(Debug或Release目录,project的dsp文件所在目录,或环境变量 PATH中所指定的目录)。 一个最简单的办法是,要么添加一个post-build命令,或者,将位于lib/目录下的testrunner.dll添加到你的project中来, 并定制创建步骤,将dll文件拷贝到你的“中间结果”目录(通常是Debug或Release目录)。
因为TestRunner GUI是一个MFC的扩展DLL,它能够访问当前应用程序的CWinApp。 参数设置将使用应用程序的注册键。这意味着,设置项“最近使用的测试”对每个应用程序而言都是不同的。
简言之:
你必须在VC++中注册该插件。在Tools/Customize/Add-ins and Macro files中点击browse,并选择lib/TestRunnerDSPlugIn.dll(你可以注册release版或者debug版,都能运 行)。
若VC++正在运行,当你双击一个failure后,VC++将打开相关文件并定位到出错行。 # 使用Test Plug In Runner:
你的DLL必须导出(export)一个函数,该函数实现了在 include/msvc6/testrunner/TestPlugInInterface.h中所定义的接口。作为范例,参见 examples/msvc6/TestPlugIn/TestPlugInInterfaceImpl.*。注意:该runner仍处于实验阶段并未作 足够多的测试。