设计原则与模式: 案例介绍--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, 但可以包含很多其它 Testclass
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 与 TestListenervoid
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 与 tearDownclass
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 添加此功能?
相关文章推荐
- 设计模式之原则详细介绍
- 《24种设计模式介绍与6大设计原则》——状态模式
- 24种设计模式介绍与6大设计原则(PHP版)之代理模式
- 设计模式介绍、分类、原则
- Java设计模式中单一职责原则(SRP)介绍
- JAVA开发常用设计模式介绍、设计模式案例
- Java 23种设计模式案例:原则及分类
- 设计模式介绍和原则
- 设计模式六大原则
- 设计模式六大原则
- 设计模式中重要的几条原则复习
- 万变不离其宗——设计模式六大原则
- 设计模式的六大原则
- 设计模式遵循的七大原则
- openfire源码研究笔记:对设计模式及原则的学习
- 设计模式六大原则(3):依赖倒置原则
- 解读设计模式--单一职责原则
- 设计模式出现之前的几大原则
- [设计模式原则]单一职责原则(Single Responsibility Principle,SRP)
- [设计模式]面向对象设计原则之里氏替换原则