全面了解CPPUnit

全面了解CPPUnit

一、   单元测试与CPPUNIT简介

几乎每个开发人员都写过测试代码,但是往往这些代码不系统,也没有良好的管理,同时在测试代码编写过程中也有很多重复的劳动,比较繁琐。在一个软件开发过程中,往往会进行很多修改,迭代开发的模型随处可见,如何验证程序的功能、性能和结构是否符合要求是一项重要的工作。

单元测试是属于白盒测试和结构性测试,一般由开发人员开展,当然如果有好的测试工具支持,测试人员甚至最终用户都可以参与。单元测试框架是编写和运行单元测试的软件工具,用来构建测试、运行测试、报告测试结果。对于C/C++开发,比较著名的收费单元测试工具是C++ Test,免费开源的则是CPPUNIT。

CPPUNIT是基于 LGPL 的开源项目,最初版本移植自 JUNIT ,是一个非常优秀的开源测试框架。CPPUNIT和 JUNIT 一样主要思想来源于极限编程。主要功能就是对单元测试进行管理,并可进行自动化测试。CPPUNIT设计遵循很多设计模式,代码结构也相对好理解。

本文将首先讲述CPPUNIT的使用(Linux环境),然后着重深入分析CPPUNIT框架的代码(覆盖了CPPUNIT的所有主干代码),对于想改造CPPUNIT或者程序设计上学习借鉴都起到抛砖引玉的作用。

二、   CPPUNIT安装、使用和框架

1.       安装

CPPUNIT的主页是http://sourceforge.NET/projects/cppunit/,从这里可以获取它的源代码。安装过程也是非常标准的,如下:

Tar zxvf cppunit-1.12.0.tar.gz

cd cppunit-1.12.0

./configure

make

make install

备注:CPPUNIT默认会把头文件和函数库分别安装到/usr/local/include和/usr/local/lib(除非在configure的时候指定prefix),所以编写测试程序的时候需要能搜索/usr/local/include和/usr/local/lib目录,否则会出错。

2.       使用

在CPPUNIT的主目录下有example/simple下的例子代码Main.cpp ExampleTestCase.h/cpp,只要这三个文件即可构造一个CPPUNIT的测试程序。

g++ -o mytest Main.cpp ExampleTestCase.cpp -lcppunit -ldl

一般通过派生TestFixture来编写用户自己的测试类,对于一个实际的测试程序,可能会有很多独立的测试类,因此可以仿照ExampleTestCase.h/cpp编写更多的测试类,而Main.cpp是不用变的,然后在编译(实际上往往写makefile)的时候加上那些源文件即可。

g++ -o mytest Main.cpp MyTestCase1.cpp MyTestCase2.cpp MyTestCase3.cpp ..... -lcppunit -ldl

3.       框架

本节把CPPUNIT的框架分为三个部分进行简单介绍。

1)       测试对象族

CPPUNIT的测试对象的类关系图:

Test:所有测试对象类的抽象基类,主要是定义run方法和统计子对象个数和查找遍历子对象的方法;

TestFixture:该类非常简单,只定义了两个方法setUp和tearDown,作为测试对象的准备和拆除方法,一般用户编写的测试类都直接继承它;

TestComposite、TestLeaf:根据设计模式中组合模式而设计的两个类,都继承自Test;

TestSuite:具体化了TestComposite的内容存储方式、添加子对象接口等等。该类对象包含了若干测试对象,作为测试对象的容器,而且可以嵌套;

TestRunner: 控制测试对象的构造和测试对象执行的类;

TestCase: 定义了一个测试对象要实现的具体接口,同时继承TestFixture的setUp和tearDown;

接口

TestCaller: 使用了设计模式中的策略模式,作为测试对象的最终封装类,提供了测试运行的策略,在测试执行中扮演了重要的角色。它是一个模板类。

2)       信息收集与显示族

CPPUNIT的测试信息收集与显示的类关系图

Outputter:是所有测试输出类的抽象基类,定义了write方法;

CompilerOutputter:以编译器信息类似方式输出测试信息,使用TestResultCollector获取测试信息;

TextOutputter:以文本流的方式输出测试信息,同样使用TestResultCollector获取测试信息;

TestListener:以设计模式中观察者模式定义了Observer所应该具有的从TestResult获取测试步骤信息的方法;

TestSuccessListener:实现了TestListener接口,同时继承了SynchronizedObject了从而具有线程安全性;

SynchronizedObject:该类实现了lock和unlock操作;

ExclusiveZone:使用SynchronizedObject进行了临界区的加锁和解锁操作;

TestResult:这个测试信息的收集者,在观察者模式中扮演Subject角色,是它把测试的各个步骤的信息通知到所有Listener对象的。

3)       测试对象管理族

CPPUNIT测试对象管理类关系图

TestFactory:运用了设计模式中工厂设计模式,这里只定义了一个makeTest方法,是一个抽象基类;

TestSuiteFactory:该类继承自TestFactory,而且是模板类,是生成TestSuite对象的工厂;

TestFactoryRegistry:管理TestFactory对象的类(这里继承自TestFactory个人感觉有点不太恰当);

AutoRegisterSuite:模板类,自动把特定的TestSuiteFactory对象注册到TestFactoryRegistry对象;

TestSuiteBuilderContextBase、TestSuiteBuilderContext:用于构建测试对象的类,详细见代码分析部分。

三、   剖析CPPUNIT

下面以CPPUNIT 1.12.0版本(当前最新版本)源码为对象,把CPPUNIT程序包中的example程序作为剖析对象,这个程序也是我们使用CPPUNIT的经典结构(以下红色的代码片段起强调作用,红色的注释是额外添加的以增强理解)

1.       预编译前C++源代码

int main( int argc, char* argv[] )

{

// Create the event manager and test controller

CPPUNIT_NS::TestResult controller;

// Add a listener that colllects test result

CPPUNIT_NS::TestResultCollector result;

controller.addListener( &result );

// Add a listener that print dots as test run.

CPPUNIT_NS::BriefTestProgressListener progress;

controller.addListener(&progress);

// Add the top suite to the test runner

CPPUNIT_NS::TestRunner runner;

runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest());

runner.run(controller);

// Print test in a compiler compatible format.

CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCout() );

outputter.write();

return result.wasSuccessful() ? 0 : 1;

}

 

ExampleTestCase.h

class ExampleTestCase : public CPPUNIT_NS::TestFixture

{

//开始构造一个TestSuite,名字就是ExampleTestCas

CPPUNIT_TEST_SUITE( ExampleTestCase );

//添加一个TestCase(测试方法

CPPUNIT_TEST( example );

//添加一个TestCase(测试方法

CPPUNIT_TEST( anotherExample );

//添加一个TestCase(测试方法)

CPPUNIT_TEST( testAdd );

//添加一个TestCase(测试方法)

CPPUNIT_TEST( testEquals );

//构造TestSuite完毕

CPPUNIT_TEST_SUITE_END();

protected:

double m_value1;

double m_value2;

public:

       void setUp();

protected:

void example();

void anotherExample();

void testAdd();

void testEquals();

};

//取出构造的TestSuite加入到全局的管理目录

CPPUNIT_TEST_SUITE_REGISTRATION( ExampleTestCase );

 

ExampleTestCase.cpp

void ExampleTestCase::example()

{

//CPPUNIT_*开头的是一些断言的宏,如果断言失败则抛出异常

//使用的时候可以参考TestAssert.H头文件

CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, 1.1, 0.05 );

CPPUNIT_ASSERT( 1 == 0 );

CPPUNIT_ASSERT( 1 == 1 );

}

void ExampleTestCase::anotherExample()

{

                  CPPUNIT_ASSERT (1 == 2);

}

void ExampleTestCase::setUp()

{

m_value1 = 2.0;

m_value2 = 3.0;

}

void ExampleTestCase::testAdd()

{

double result = m_value1 + m_value2;

CPPUNIT_ASSERT( result == 6.0 );

}

void ExampleTestCase::testEquals()

{

long* l1 = new long(12);

long* l2 = new long(12);

CPPUNIT_ASSERT_EQUAL( 12, 12 );

CPPUNIT_ASSERT_EQUAL( 12L, 12L );

CPPUNIT_ASSERT_EQUAL( *l1, *l2 );

delete l1;

delete l2;

 

CPPUNIT_ASSERT( 12L == 12L );

CPPUNIT_ASSERT_EQUAL( 12, 13 );

CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 );

}

2.       预编译后代码

main函数没变化,ExampleTestCase.h/cpp由于包含较多宏,所以变化较大(红色代码是宏展开后的代码),如下所示:

ExampleTestCase.cpp(include "ExampleTestCase.h")

class ExampleTestCase : public CppUnit::TestFixture

{

public:

       typedef ExampleTestCase TestFixtureType;

private:

//主要是利用C++的RTTI信息取得用户写的TestFixture类的类名字

static const CppUnit::TestNamer &getTestNamer__()

{

static CppUnit::TestNamer testNamer( typeid(ExampleTestCase) );

return testNamer;

}

public:

typedef CppUnit::TestSuiteBuilderContext<TestFixtureType> TestSuiteBuilderContextType;

static void addTestsToSuite( CppUnit::TestSuiteBuilderContextBase &baseContext )

{

//把每个测试方法用TestCaller使用具体的TestFixture类型封装起来,加入

//TestSuite中

TestSuiteBuilderContextType context( baseContext );

//生成TestCaller封装测试代码,加入TestSuite

context.addTest( (new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "example"), &TestFixtureType::example, context.makeFixture() ) ) );

context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "anotherExample"), &TestFixtureType::anotherExample, context.makeFixture() ) ) );

context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testAdd"), &TestFixtureType::testAdd, context.makeFixture() ) ) );

context.addTest( ( new CppUnit::TestCaller<TestFixtureType>( context.getTestNameFor( "testEquals"), &TestFixtureType::testEquals, context.makeFixture() ) ) );

}

 

//注意,这里suite()是static类型方法

static CppUnit::TestSuite *suite()

{

                   const CppUnit::TestNamer &namer = getTestNamer__();

                   std::auto_ptr<CppUnit::TestSuite> suite( new CppUnit::TestSuite( namer.getFixtureName() ));

                   CppUnit::ConcretTestFixtureFactory<TestFixtureType> factory;

                   CppUnit::TestSuiteBuilderContextBase context(*suite.get(), namer, factory);

                   TestFixtureType::addTestsToSuite( context );

                   return suite.release();

}

private:

                   typedef int CppUnitDummyTypedefForSemiColonEnding__;

protected:

                   double m_value1;

                   double m_value2;

public:

                   //setUp和tearDown是TestFixture的虚拟函数,子类可以不实现。

                   void setUp();

                   //void tearDown();

                   protected:

                   //以下是用户自己编写的测试方法,至于为什么返回类型都是void,而且无参数否则编译会不通过,在后面分析TestCaller的时候会讲到原因。

                   void example();

                   void anotherExample();

                   void testAdd();

                   void testEquals();

};

//在AutoRegisterSuite实例对象的构造函数中完成了TestSuite的登记操作

static CppUnit::AutoRegisterSuite< ExampleTestCase > autoRegisterRegistry__4;

 

void ExampleTestCase::example()

{

//下面这些方法,当断言条件不成立的时候则抛出Exception,Exception中有断言的表达

//式和代码位置信息,这个Exception由外层处理并记录到TestResult

(

                   CppUnit::assertDoubleEquals( (1.0), (1.1), (0.05),CppUnit::SourceLine( "ExampleTestCase.cpp", 8 ), "" ) );

                   ( CppUnit::Asserter::failIf( !(1 == 0), CppUnit::Message( "assertion failed", "Expression: " "1 == 0"), CppUnit::SourceLine( "ExampleTestCase.cpp", 9 ) ) );

                   ( CppUnit::Asserter::failIf( !(1 == 1), CppUnit::Message( "assertion failed", "Expression: " "1 == 1"), CppUnit::SourceLine( "ExampleTestCase.cpp", 10 ) ) );

}

void ExampleTestCase::anotherExample()

{

                   ( CppUnit::Asserter::failIf( !(1 == 2), CppUnit::Message( "assertion failed", "Expression: " "1 == 2"), CppUnit::SourceLine( "ExampleTestCase.cpp", 16 ) ) );

}

void ExampleTestCase::setUp()

{

                   m_value1 = 2.0;

                   m_value2 = 3.0;

}

void ExampleTestCase::testAdd()

{

                   double result = m_value1 + m_value2;

                   ( CppUnit::Asserter::failIf( !(result == 6.0), CppUnit::Message( "assertion failed", "Expression: " "result == 6.0"), CppUnit::SourceLine( "ExampleTestCase.cpp", 28 ) ) );

}

void ExampleTestCase::testEquals()

{

                   long* l1 = new long(12);

                   long* l2 = new long(12);

                   ( CppUnit::assertEquals( (12), (12),CppUnit::SourceLine( "ExampleTestCase.cpp", 37 ), "" ) );

                   ( CppUnit::assertEquals( (12L), (12L),CppUnit::SourceLine( "ExampleTestCase.cpp", 38 ), "" ) );

                   ( CppUnit::assertEquals( (*l1), (*l2),CppUnit::SourceLine( "ExampleTestCase.cpp", 39 ), "" ) );

                   delete l1;

                   delete l2;

                   ( CppUnit::Asserter::failIf( !(12L == 12L), CppUnit::Message( "assertion failed", "Expression: " "12L == 12L"), CppUnit::SourceLine( "ExampleTestCase.cpp", 44 ) ) );

                   ( CppUnit::assertEquals( (12), (13),CppUnit::SourceLine( "ExampleTestCase.cpp", 45 ), "" ) );

                   ( CppUnit::assertDoubleEquals( (12.0), (11.99), (0.5),CppUnit::SourceLine( "ExampleTestCase.cpp", 46 ), "" ) );

}

3.       源码解读

使用CPPUNIT进行单元测试的程序一般包含三个过程(从main函数可以看出来)

l  测试对象的构建

l  运行测试对象

l  测试结果信息收集与显示(在每一个代码片段后面都会给出函数调用序列)。

                   (具体参考源代码。源文排版混乱,不做转载)

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值