在Windows环境下基于VC6.0的CppUnit使用

摘要
测试驱动开发(TDD)是以测试作为开发过程的中心,它坚持在编写实际代码之前,先写好基于产品代码的测试代码。开发过程的目标就是首先使测试能够通过,然后再优化设计结构。XUnit是一个基于测试驱动开发的测试框架,它为我们在开发过程中使用测试驱动开发提供了一个方便的工具,使我们得以快速的进行单元测试。XUnit的成员有很多,如JUnitPythonUnit等。今天论文中讨论的CppUnit 即是XUnit家族中的一员,它是一个专门面向C++的测试框架

一、引言

CppUnit是基于LGPL的开源项目,最初版本移植自JUnit,是个非常优秀开源测试框架。CppUnitJUnit样主要思想来源于编程,主要功能是对单元测试进行管理并可进行自动化测试,CppUnit设计模式代码也相对好理解。实验使用的CppUnit的最新版本1.12.1,开发环境为WindowsXpVC6.0

本文不对CppUnit源码做详细的介绍,而只是对CppUnitVC6.0环境下的应用作一些介绍。文章的安排如下:第二部分介绍CppUnit源代码的组成;第三部分介绍CppUnit的基本框架和概念;第四部分说明CppUnit的安装与配制,在VC中的使用,讨论怎样为产品代码添加测试代码(实际上应该反过来,为测试代码添加产品代码。在TDD中,先有测试代码后有产品代码),并通过CppUnit来进行测试;第五部分列出了实验的测试结果。

二、CppUnit源代码的组成

CppUnit是开源产品,从http://sourceforge.net/projects/cppunit 下载源码包,当前最高版本为1.12.1下载后,将源码包解压缩到本地硬盘,例如解压到D: cppunit-1.12.1。下载解压后,你将看到如下文件夹,如图1

1 CppUnit源代码的组成

主要的文件夹有:

doc: CppUnit的说明文档。另外,代码的根目录,还有三个说明文档,分别是INSTALLINSTALL-unixINSTALL-WIN32.txt

examples: CpppUnit提供的例子,也是对CppUnit自身的测试,通过它可以学习如何使用CppUnit测试框架进行开发;

  include: CppUnit头文件;

  src: CppUnit源代码目录;

  config:配置文件;

  contribcontribution,其他人贡献的外围代码;

  lib:存放编译好的库;

  src:源文件,以及编译库的project等;

接下来进行编译工作。 在src/目录下CppUnitLibraries.dsw工程文件用vc 打开。执行build/batch build,编译成功的话,生成的库文件将被拷贝到lib目录下。中途或者会有些project编译失败,一般不用管它,我们重点看的是cppunit和 TestRunner 这两个projectdebugrelease版本。

  编译通过以后lib/目录下,会生成若干lib,dll文件都以cppunit开头. cppunitd表示debug, cppunit表示release版。

  CppUnit为我们提供了两套框架库,一个为静态的lib,一个为动态的dllcppunit project:静态libcppunit_dll project:动态dlllib。在开发中我们可以根据实际情况作出选择。可以根据需要选择所需的项目进行编译,其中项目cppunit为静态库,cppunit_dll为动态库,生成的库文件为:

  cppunit.lib:静态库release版;

  cppunitd.lib:静态库debug版;

  cppunit_dll.lib:动态库release版;

  cppunitd_dll.lib:动态库debug版;

  另外一个很重要的projectTestRunner,它输出一个dll,提供了一个基于GUI 方式的测试环境,在CppUnit可以选择控制台方式和GUI方式两种表现方案。两种方案分别如下图所示,图2GUI方式,图3为文本方式:

2 GUI方式

控制台方式

要使用CppUnit,还得设置好头文件和库文件路径,以VC6.0为例,选择Tools/Options/Directories,在Include filesLibrary files中分别添加文件所在的路径。本文这里分别填的是D:\CPPUNIT-1.12.1\INCLUDED:\CPPUNIT-1.12.1\LIB如图4所示:

包含相应的文件

三、CppUnit的基本框架和概念

CppUnit的基本框架与JUnit类似,核心内容主要包括一些关键类,如Test类、TestFixture类、TestCase类、TestSuite类、TestFactory类及TestRunner类。

1Test:所有测试对象的基类。

CppUnit采用树形 结构来组织管理测试对象(类似于目录树,如下图所示),因此这里采用了组合设计模式(Composite Pattern),Test的两个直接子类TestLeafTestComposite分别表示测试树中的叶节点和非叶节点,其中 TestComposite主要起组织管理的作用,就像目录树中的文件夹,而TestLeaf才是最终具有执行能力的测试对象,就像目录树中的文件。

Test最重要的一个公共接口run,定义为virtual void run(TestResult *result) = 0;其作用为执行测试对象,将结果提交给result

  在实际应用中,我们一般不会直接使用TestTestComposite以及TestLeaf,除非我们要重新定制某些机制。

2TestFixture:用于维护一组测试用例的上下文环境。

  在实际应用中,我们经常会开发一组测试用例来对某个类的接口加以测试,而这些测试用例很可能具有相同的初始化和清理代码。为此,CppUnit引入TestFixture来实现这一机制。

  TestFixture具有以下两个接口,分别用于处理测试环境的初始化与清理工作:

virtual void setUp();
virtual void tearDown();

3TestCase:测试用例,从名字上就可以看出来,它便是单元测试的执行对象。

TestCaseTestTestFixture多继承而来,通过把Test::run制定成模板函数(Template Method)而将两个父类的操作融合在一起,用户需从TestCase派生出子类并实现runTest以开发自己所需的测试用例。另外还有一个重要的就是TestResultprotect方法,其作用是对执行函数(实际上是函数对象)的错误信息(包括断言和异常等)进行捕获,从而实现对测试结果的统计。

4TestSuite:测试包,按照树形结构管理测试用例

  TestSuitTestComposite的一个实现,它采用vector来管理子测试对象(Test),从而形成递归的树形结构。

5TestFactory:测试工厂

这是一个辅助类,通过借助一系列宏定义让测试用例的组织管理变得自动化。可以参见第三部分的例子。

6TestRunner:用于执行测试用例

TestRunner将待执行的测试对象管理起来,然后供用户调用。其接口为

virtual void addTest( Test *test );
virtual void run( TestResult &controller, const std::string &testPath = "" );

这也是一个辅助类,需注意的是,通过addTest添加到TestRunner中的测试对象必须是通过new动态创建的,用户不能删除这个对象,因为TestRunner将自行管理测试对象的生命期。

四、CppUnit的使用

1CppUnit的配制

了解了以上的知道就可以正式使用CppUnit了,由于单元测试是TDD(测试驱动开发)的利器,一般人会先写测试代码,然后再写产品代码。CppUnit最小的测试单位是TestCase,多个相关TestCase组成一个TestSuite。要添加测试代码最简单的方法就是利用CppUnit为我们提供的几个宏来进行(当然还有其他的手工加入方法,但均是殊途同归,可以查阅CppUnit头文件中的演示代码)。这几个宏是:

CPPUNIT_TEST_SUITE() 开始创建一个TestSuite
CPPUNIT_TEST() 添加TestCase
CPPUNIT_TEST_SUITE_END() 结束创建TestSuite
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION() 添加一个TestSuite到一个指定的TestFactoryRegistry工厂。

假定我们要实现一个类,类名暂且取做Calculator,它的功能主要是实现两个数加减乘除。 假定这个类要实现了四个方法是:

int add(int nNum1, int nNum2);

int substract(int nNum1, int nNum2);

int multiply(int nNum1, int nNum2);

int divide(int nNum1, int nNum2);

   测试驱动开发先写测试代码,后写产品代码(Calculator),先写的测试代码往往是不能运行或编译的,目标是在写好测试代码后写产品代码,使之编译通过,然后再进行重构。这就是Kent Beck说的“red/green/refactor”。所以,上面的类名和方法应该还只是在程序员的心里,还只是一个idea而已。

根据测试驱动的原理,我们需要先建立一个单元测试框架。在VC中为测试代码建立一个project。通常,测试代码和被测试对象(产品代码)是处于不同的project中的。这样就不会让你的产品代码被测试代码所污染 

由于在CppUnit可以选择控制台方式和UI方式两种表现方案,本文中选择UI方式。在本例中,我们将建立一个基于GUI 方式的测试环境。因此我们建立一个基于对话框的Project。假设名为UnitTest建立了UnitTest project之后,首先配置这个工程。

首先在project中打开RTTI开关,具体位置在菜单Project/Settings/C++/C++ Language。如下图所示设置:

打开RTTI

最后应该在project连接正确的lib。包括本例采用的cppunit.libcppunitd.lib静态库以及用于GUI方式的TestRunner.dll对应的lib。具体位置在Project/Settings/Link/General  在‘Object/library modules’中,针对debugrelease分别加入cppunitd.lib testrunnerd.libcppunit.lib TestRunner.lib。由于TestRunner.dll为我们提供了基于GUI的测试环境。为了让我们的测试程序能正确的调用它,TestRunner.dll必须位于 你的测试程序的路径下。所以把/lib目录下的testrunnerd.dllTestRunner.dll文件分别拷贝到UnitTest priject的程序debugrelease版本输出目录中。

2、程序的测试框架

配置工作后,下面开始写测试框架。在CppUnit是以TestCase为最小的测试单位若干TestCase组成一个TestSuite。所以我们要先建立一个TestCase

UnitTest project中新建一个类命名为CCalTestCase, 让其从CppUnit::TestCase派生。为其新增一个方法,假设为 void testAdd(); 我们将在这个函数中写入我们的一些测试代码。代码如下: 

  1. #include <cppunit/TestCase.h>  
  2. class CCalTestCase : public CppUnit::TestCase  
  3. {  
  4. public:  
  5.   CCalTestCase ();  
  6.   virtual ~ CCalTestCase ();  
  7.   void testAdd();  
  8. };  
接下来 我们要对我们的 C CalTestCase 进行声明。声明用到了三个宏

CPPUNIT_TEST_SUITE();
CPPUNIT_TEST();
CPPUNIT_TEST_SUITE_END();

第一个宏声明一个测试包(suite,第二个宏声明(添加)一个测试用例。 现在我们的CCalTestCase类看上去像这样:切记要包含头文件,否则无法识别这些宏。

  1. #include <cppunit/TestCase.h>  
  2. #include <CppUnit/extensions/HelperMacros.h>  
  3. class CCalTestCase : public CppUnit::TestCase  
  4. {  
  5.   CPPUNIT_TEST_SUITE(CCalTestCase);  
  6.   CPPUNIT_TEST(testAdd);  
  7.   CPPUNIT_TEST_SUITE_END();  
  8. public:  
  9.   CCalTestCase ();  
  10.   virtual ~ CCalTestCase ();  
  11.   void testAdd();  
  12. };  
通过这几个宏,我们就把 C CalTestCase testAdd 注册到了测试列表当中。接下来 , 我们要注册我们的测试 suite

使用CPPUNIT_TEST_SUITE_NAMED_REGISTRATION()来注册一个测试suite。这个宏的第二个参数是我们注册的suite的名字。在这里我们可以用字符串来代替,但我们用一个静态函数来返回这个suite的名字

  1. // CalTestCase.cpp  
  2. std::string  
  3. CCalTestCase::GetSuiteName()  
  4. {  
  5.     return " Calculator ";  
  6. }  
然后在 CalTestCase.cpp 注册我们的 suite

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CCalTestCase, CCalTestCase::GetSuiteName());它将CCalTestCase这个TestSuite注册到一个指定的TestFactory工厂中。接下来我们写一个注册函数static CppUnit::Test* GetSuite(), 使其在运行期生成一个Test

  1. // CalTestCase.h  
  2. class CCalTestCase : public CppUnit::TestCase  
  3. {  
  4.  CPPUNIT_TEST_SUITE(CCalTestCase);  
  5.   CPPUNIT_TEST(testAdd);  
  6.   CPPUNIT_TEST_SUITE_END();  
  7. public:  
  8.   CCalTestCase ();  
  9.   virtual ~ CCalTestCase ();  
  10.   void testAdd();  
  11.   static std::string GetSuiteName();  
  12.   static CppUnit::Test* GetSuite();  
  13. };  
  14.  // CalTestCase.cpp  
  15.  CppUnit::Test* CCalTestCase::GetSuite()  
  16.  {  
  17.   CppUnit::TestFactoryRegistry& reg =  
  18.     CppUnit::TestFactoryRegistry::getRegistry (CCalTestCase::GetSuiteName());  
  19.   return reg.makeTest();  
  20.    }  
记住在 CalTestCase.h 中包含头文件 :

#include <cppunit/extensions/TestFactoryRegistry.h>最后我们为单元测试建立一个UI测试界面。由于我们希望这个Project运行后显示的是GUI界面,所以我们需要在App的 InitInstance ()中屏蔽掉原有的对话框,代之以CppUnitGUI在CUnitTestApp::InitInstance()函数中,将原先显示主对话框的代码以下面的代码取代:

  1. CppUnit::MfcUi::TestRunner runner;  
  2.   runner.addTest(CCalTestCase::GetSuite());//添加测试  
  3.   runner.run();//show UI  
  4. /*  CUnitTestDlg dlg; 
  5.   m_pMainWnd = &dlg; 
  6.   int nResponse = dlg.DoModal(); 
  7.   if (nResponse == IDOK) 
  8.   { 
  9.     // TODO: Place code here to handle when the dialog is 
  10.     // dismissed with OK 
  11.   } 
  12.   else if (nResponse == IDCANCEL) 
  13.   { 
  14.     // TODO: Place code here to handle when the dialog is 
  15.     // dismissed with Cancel 
  16.   } 
  17. */  
必须先在 UnitTest.cpp 中包含头文件 :

#include <cppunit/ui/mfc/TestRunner.h>

#include " CalTestCase.h "

到此为止我们已经建立好一个简单的单元测试框架。测试框架虽然写好了,但是测试代码仍然为空,产品代码也还没有写。

3、测试代码

如前所述,在测试类中,我们添加了一个测试方法:void testAdd();它测试的对象是前面提到的CCalculator类的方法:int Add(int nNum1, int nNum2);(产品代码)我们来看看testAdd()的实现,在CalTestCase.h中包含头文件#include <cppunit/TestAssert.h>,测试代码如下:

  1.  // CalTestCase.cpp  
  2.  void CCalTestCase::testAdd()  
  3.  {  
  4.     CCalculator cal;  
  5.     int nResult = cal.add(10, 20);     //执行Add操作  
  6.     CPPUNIT_ASSERT_EQUAL(30, nResult); //检查结果是否等于30    
  7.  }  
  8.   
  9. void CCalTestCase::testSub()  
  10.   
  11. {  
  12.   
  13. Calculator cal;  
  14.   
  15. int nResult = cal.substract(10, 20); //执行Substract操作  
  16.   
  17. CPPUNIT_ASSERT_EQUAL(-10, nResult); //检查结果是否等于-10    
  18.   
  19. }  
  20.   
  21. void CCalTestCase::testMul()  
  22.   
  23. {  
  24.   
  25. Calculator cal;  
  26.   
  27. int nResult = cal.multiply(10, 20); //执行Multiply操作  
  28.   
  29. CPPUNIT_ASSERT_EQUAL(200, nResult); //检查结果是否等于200    
  30.   
  31. }  
  32.   
  33. void CCalTestCase::testDiv()  
  34.   
  35. {  
  36.   
  37. Calculator cal;  
  38.   
  39. int nResult = cal.divide(20, 10); //执行Divide操作  
  40.   
  41. CPPUNIT_ASSERT_EQUAL(2, nResult); //检查结果是否等于2   
  42.   
  43. }  
CPPUNIT_ASSERT_EQUAL 是一个判断结果的宏。 CppUnit 中类似的其它宏请查阅 TestAssert.h ,本文在此不做详述 。另外,还可以覆写基类的  setUp() tearDown() 两个函数。这两个函数实际上是一个模板方法,在测试运行之前会调用 setUp() 以进行一些初始化的工作,测试结束之后又会调用 tearDown() 来做一些 善后工作 ”  ,比如资源的回收等等。当然,你也可以不覆写这两个函数,因为它们在基类里定义成了空方法,而不是纯虚函数。 依此方法,写出剩余的三个测试函数 testSub testMul testDiv

4、书写产品代码

在VC中建立一个MFC Extension Dll的Project,在这个Project 中加入类Calculator,它的声明如下:

  1. // Calculator.h  
  2. class Calculator    
  3. {  
  4. public:  
  5. Calculator();  
  6. virtual ~Calculator();  
  7. int add(int n,int m);  
  8. int substract(int n,int m);  
  9. int multiply(int n,int m);  
  10. int divide(int n,int m);  
  11. };  
  12.   
  13. 实现代码如下:  
  14. // Calculator.cpp  
  15. int Calculator::add(int n,int m)  
  16. {  
  17. return n+m;  
  18. }  
  19. Calculator::substract(int n,int m)  
  20. {  
  21. return n-m;  
  22. }  
  23. Calculator::multiply(int n,int m)  
  24. {  
  25. return n*m;  
  26. }  
  27. Calculator::divide(int n,int m)  
  28. {  
  29. if(m == 0)  
  30. {  
  31. return MAXNUM;  
  32. }  
  33. return n/m;  
  34. }  
五、测试结果

在包含测试代码的Project(即UnitTest dependent这个产品代码,并且include 相关头文件 ,Rebuild All,执行得如下结果:

图6 实现的四个测试TestMethod

图7 测试结果


转载自:

http://blog.csdn.net/pengtwelve/article/details/7173254


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值