您的位置:首页 > 编程语言 > C语言/C++

设计原则与模式: 案例介绍--CppUnit

2008-06-01 20:15 351 查看

设计原则与模式: 案例介绍--CppUnit

CppUnit 是一个单元测试框架, 我们看一看它的设计是如何遵循基础的设计原则和模式的

单一职责原则

TestRunner 和 TestResult 的分离

class

CPPUNIT_API TestRunner {

virtual

void
addTest( Test *test );

virtual

void
run( TestResult &result,
const
std::string &testPath =
""
);

...

};

TestRunner 负责收集并运行测试用例, 但并不主动打印测试结果. 测试结果被收集在 TestResult 对象中, 可以以各种形式被处理:

CPPUNIT_NS::TestResult controller;

CPPUNIT_NS::TestResultCollector result;

controller.addListener( &result );

...

runner.run( controller );

// Print test in a compiler compatible format.

CPPUNIT_NS::CompilerOutputter compiler_outputter( &result, CPPUNIT_NS::stdCOut() );

compiler_outputter.write();

// Print test in XML format.

CPPUNIT_NS::XmlOutputter xml_outputter( &result, CPPUNIT_NS::stdCOut() );

xml_outputter.write();

开放封闭原则

遵循开放封闭原则的一个重要特征就是 "针对接口/基类编程", 任何根据 typeid 等类型信息进行的分支处理如 if/else, switch/case 等都可以看做是破坏开放封闭原则的前兆

TestResult 对于如何处理测试过程中发生的事件是开放的, 可以通过 TestListener 来扩展

class
CPPUNIT_API TestResult : protected
SynchronizedObject {

virtual
void
addListener( TestListener *listener );

virtual
void
removeListener( TestListener *listener );

/// Informs TestListener that a test will be started.

virtual
void
startTest( Test *test );

...

};

void
TestResult::startTest( Test *test ) {

...

for
( TestListeners::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it )

(*it)->startTest( test );

}

};

void
BriefTestProgressListener::startTest

(
Test
*test ) {

stdCOut

() << test->
getName

();

stdCOut

().flush();

m_lastTestFailed
=
false

;

}

void

TextTestProgressListener::startTest

( Test *test ) {

stdCOut() <<
"."
;

}

接口分离原则

从 TestRunner 的角度来讲, 它只需要把测试用例 "运行" 起来, 并不需要关心测试用例是如何运行的, 如何搭建和清理运行环境, 它所需要的 Test 对象的接口, 只需要一个 run( ) 方法 :

class

CPPUNIT_API
Test
{

virtual


void

run
(
TestResult
*result ) =0;

};

void


TestRunner::addTest

(
Test
*test ) {

m_suite
->
addTest

( test );

}

void


TestRunner::run

(
TestResult
&controller,

const

std::
string
&testPath )
{

TestPath
path =
m_suite
->
resolveTestPath

( testPath );

Test
*testToRun = path.getChildTest();

controller.
runTest

( testToRun );

}

但是总有一个类需要负责搭建和清理测试环境:
class

CPPUNIT_API
TestFixture
{

virtual


void

setUp
() {};

virtual


void

tearDown
() {};

};

class

CPPUNIT_API
TestCase
:
public


Test
,

public


TestFixture
{

};

TestCase
这个类分别实现了Test

和TestFixture
两个分离的接口, 这样TestCase
的客户只需要按需依赖 Test
或 TestFixture
就可以了


依赖倒置原则

后来被发展为依赖注入或者控制反转模式,思想就是依赖于抽象,而不是具体的类

TestRunner 并不依赖具体的 TestCase/TestSuite 等, 它只依赖于 Test. 在main函数中由 TestFactoryRegistry::makeTest() 产生 具体的Test对象, 注入到TestRunner中

class

CPPUNIT_API TestRunner {

virtual

void

addTest( Test *test );

virtual

void
run( TestResult &result,
const
std::string &testPath =
""

);

...

};

int
main

(
int

argc,
char

* argv[] ) {

// Add the top suite to the test runner

CPPUNIT_NS::
TestRunner
runner;

runner.
addTest

( CPPUNIT_NS::
TestFactoryRegistry
::
getRegistry

().
makeTest

() );

runner.
run

( controller );

...

}

Test
*
TestFactoryRegistry::makeTest

() {

TestSuite
*suite =
new


TestSuite

(
m_name
);

addTestToSuite

( suite );

return

suite;

}

Composite 模式

TestSuite 本身也是 Test, 但可以包含很多其它 Test

class

CPPUNIT_API
TestComposite
:
public


Test
{

...

};

class

CPPUNIT_API
TestSuite
:
public


TestComposite
{

void

addTest
(
Test
*test );

};

客户代码参见前面的 main

函数

Decorator 模式

1. 如何重复运行测试用例?

重复运行也是运行, 只是添加了额外的一种职责: 运行多次

class

CPPUNIT_API
TestDecorator
:
public


Test
{

...

};

class

CPPUNIT_API
RepeatedTest
:
public

TestDecorator {

RepeatedTest
(
Test
*test,

int

timesRepeat ) :

TestDecorator
( test ),

m_timesRepeat
(timesRepeat)
{

}

void

run
(
TestResult
*result ) {

for

(
int

n = 0; n < m_timesRepeat; n++ ){

TestDecorator::run( result );

}

}

...

};

2. 如何确保 TestCase 的隔离性, 即使出现异常也不影响后续 TestCase 的运行 ?

ProtectedFunctor -> Functor -> 函数指针

class


TestCaseMethodFunctor
:
public


Functor
{

typedef


void

(
TestCase
::*
Method
)();

TestCaseMethodFunctor
(
TestCase
*target,

Method
method )
:
m_target
( target ),
m_method
( method )
{

}

bool

operator()
()
const


{

(
m_target
->*
m_method
)();

return


true

;

}

...

};

bool


DefaultProtector::protect

(
const

Functor &functor,

const

ProtectorContext &context ) {

try


{

return

functor();

}

catch

( Exception &failure )
{

reportFailure( context, failure );

}

catch

( std::exception &e )
{

reportError( context,
Message(
"uncaught exception of type "
, e.what() )
);

}

catch

( ... )
{

reportError( context,
Message(
"uncaught exception of unknown type"
) );

}

return


false

;

}

练习
: 这里使用了 Decorator 模式的思想, 但并不是严格的 Decorator 模式, 为什么?

Observer 模式

TestResult 与 TestListener

void


TestResult::addListener

(
TestListener
*listener ) {

m_listeners
.push_back( listener );

}

void


TestResult::removeListener

(
TestListener
*listener ) {

removeFromSequence(
m_listeners
, listener );

}

void


TestResult::startTestRun

(
Test
*test ) {

for

(
TestListeners
::
iterator
it =
m_listeners
.begin();
it !=
m_listeners
.end();
++it )

(*it)->startTestRun( test,
this

);

}

void


TestResult::endTestRun

(
Test
*test ) {

for

(
TestListeners
::
iterator
it =
m_listeners
.begin();
it !=
m_listeners
.end();
++it )

(*it)->endTestRun( test,
this

);

}

Factory Method 模式

调用测试用例注册表来产生测试用例

class

CPPUNIT_API
TestFactoryRegistry
:
public


TestFactory
{

virtual


Test
*makeTest
();

};

Strategy 模式

不同的格式的输出, 如Compiler风格的, Xml格式的, 等等

class

CPPUNIT_API
TextTestRunner
:
public

CPPUNIT_NS::
TestRunner
{

void

setOutputter
(
Outputter
*outputter )

{

delete


m_outputter
;

m_outputter
= outputter;

}

void


printResult

(
bool

doPrintResult ) {

m_outputter
->
write

();

}

};

CPPUNIT_NS::
TextTestRunner
runner;

CPPUNIT_NS::
CompilerOutputter
compiler_outputter
( ... );

runner.setOutputter(compiler_outputter);

Template Method 模式

TestFixture 的 setUp 与 tearDown

class

CPPUNIT_API
TestFixture
{

virtual


void

setUp
() {};

virtual


void

tearDown
() {};

};

void


TestCase::run

(
TestResult
*result ) {

result->
startTest

(
this

);

if

( result->protect( TestCaseMethodFunctor(
this

, &
TestCase
::setUp
),

this

,

"setUp() failed"
) )
{

result->protect( TestCaseMethodFunctor(
this

, &
TestCase
::
runTest

),

this

);

}

result->protect( TestCaseMethodFunctor(
this

, &
TestCase
::tearDown
),

this

,

"tearDown() failed"
);

result->
endTest

(
this

);

}

练习
: CppUnit 支持将测试结果输出到控制台, 或者文件, 但缺省并不支持同时输出到控制台和文件, 如何在遵循各种设计原则的情况下, 为 CppUnit 添加此功能?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: