首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 .NET Java 游戏 视频 人才 外包 第二书店 程序员
您的位置:软件测试频道-正文

用CPP做C单元测试2008-07-04 来自:lizhe1985  [收藏到我的网摘]

来源:51Testing软件测试网

  介绍:

  在QA中,主要有两种测试

  单元测试:验证我们系统中的所有逻辑单元的验证行为(并不考虑其他单元的相互关系,比如其他的可以打成桩函数等。)

  系统测试(集成测试)各个单元之间的相互关系,检测系统运行行为。

  单元测试用例设计

  在开发过程中,程序员通常用调试器来测试他们的程序,但是很少有人去单步调试程序,不会检测每个可能的变量值,这样我们就要借助一些工具来完成。就是我们所说的“单元测试框架”来测试我们的程序。

  我们来测试一个简单的c程序

   BOOL addition(int a, int b)
  {
   return (a + b);
  }

  我们的用例必须借助其他的c函数来完成验证所有的可能性,返回True或者False来说明测试是否通过

   BOOL additionTest()
  {
   if ( addition(1, 2) != 3 )
    return (FALSE);
   if ( addition(0, 0) != 0 )
    return (FALSE);
   if ( addition(10, 0) != 10 )
    return (FALSE);
   if ( addition(-8, 0) != -8 )
    return (FALSE);
   if ( addition(5, -5) != 0 )
    return (FALSE);
   if ( addition(-5, 2) != -3 )
    return (FALSE);
   if ( addition(-4, -1) != -5 )
    return (FALSE);
   return (TRUE);
  }

  我们看到,测试所有的可能性需要

  正数+负数, 0+0, 负数+0, 正数+0,正数+正数,负数+正数,负数+负数

  每个cases比较了加的结果和期望值,如果不通过就False,如果都通过就返回True

  行为上可以设计下面的例子:

   int additionPropertiesTest()
  {
   // conmutative: a + b = b + a
   if ( addition(1, 2) != addition(2, 1) )
    return (FALSE);
   // asociative: a + (b + c) = (a + b) + c
   if ( addition(1, addition(2, 3)) != addition(addition(1, 2), 3) )
    return (FALSE);
   // neutral element: a + NEUTRAL = a
   if ( addition(10, 0) != 10 )
    return (FALSE);
   // inverse element: a + INVERSE = NEUTRAL
   if ( addition(10, -10) != 0 )
    return (FALSE);
   return (TRUE);
  }

  但是这样当代码变化时用例就得跟着相应的变化,或者去加一个新的case

  XP(极限编程)推荐就是在编写代码之前先写测试用例。就是测试驱动开发。

  CPPUnit

  CPPUnit

  各Case应该被写在类里面从TestCase 导出。这个类对我们所有基本功能进行测试, 在Test Suite(测试用例集合)登记等等

  例如, 我们写了一个功能在磁盘存放一些数据的小模块。 这个模块(类名DiskData) 有主要二功能: 装载和保存数据到文件里面:

   typedef struct _DATA
  {
   int number;
   char string[256];
  }
  DATA, *LPDATA;
  class DiskData
  {
   public:
    DiskData();
    ~DiskData();
   LPDATA getData();
   void setData(LPDATA value);
   bool load(char *filename);
   bool store(char *filename);
   private:
   DATA m_data;
  };

  现在, 什么编码方式并不重要, 因为最重要事是我们必须肯定它必须做, 是这个类应该做: 正确地装载和存放数据到文件。

  为了做这个验证,我们去创造一个新的测试集,包括二个测试用例: 一个装载数据和另为存储数据。

  使用 CPPUnit

  你能在这里http://cppunit.sourceforge.net/得到最新的CPPUnit 版本, 你能发现所有的库 , 文献, 例子和其它有趣的材料。(我下载了版本为1.8.0 并且这个颁布工作良好)

  在Win32里, 你能在VC++ 之下(6.0 和以后版本)使用CPPUnit , 但是当CPPUnit 使用ANSI C++, 有少量接口时针对其它环境象C++Builder。

  在CPPUnit发布版本里面,所有建造库的步骤和信息,可以在INSTALL-WIN32.txt文件找到,。当所有二进制文件被构建之后, 你就能写你自己的测试集了。

  想在VC中写自己的测试程序,可以按照以下步骤:

  建立一个MFC的对话框(或文档视图结构)
  允许时间类型信息,Alt+F7 --> C/C++ --> C++ language --> Enable RTTI
  把Cppunit\inlude放到include目录:Tools - Options - Directories - Include.
  用cppunitd.lib (静态连接) 或者cppunitd_dll.lib (动态链接),testrunnerd.lib来链接你的程序。
  如果动态链接,就要把testrunnerd.dll 拷到应用程序目录来运行。

  Ok,看一下测试用例的类的定义吧。   #if !defined(DISKDATA_TESTCASE_H_INCLUDED)
  #define DISKDATA_TESTCASE_H_INCLUDED
  #if _MSC_VER > 1000
  #pragma once
  #endif // _MSC_VER > 1000
  #include <cppunit/TestCase.h>
  #include <cppunit/extensions/HelperMacros.h>
  #include "DiskData.h"
  class DiskDataTestCase : public CppUnit::TestCase
  {
   CPPUNIT_TEST_SUITE(DiskDataTestCase);
   CPPUNIT_TEST(loadTest);
   CPPUNIT_TEST(storeTest);
   CPPUNIT_TEST_SUITE_END();
   public:
   void setUp();
   void tearDown();
   protected:
   void loadTest();
   void storeTest();
   private:
   DiskData *fixture;
  };
  #endif

  首先, 必须包含TestCase.h和HelperMacros.h. 第一步,我们的从我们的Testcase基类配生的新类。第二,用一些宏使我们的定义的更方便,如 CPPUNIT_TEST_SUITE (开始测试定义), CPPUNIT_TEST (定义一个测试用例) 或 CPPUNIT_TEST_SUITE_END (结束一个测试集).

  我们的类(DiskDataTestCase)有重载了两个方法setUp()和tearDown(). 一个开始,一个结束测试。

  测试过程如下

  启动程序

  点击“Run”

  调用Call setUp()方法: 构建我们的测试对象fixture

  调用第一个测试方法

  调用tearDown() 方法,清除对象

  调用Call setUp()方法: 构建我们的测试对象fixture

  调用第一个测试方法

  调用Call setUp()方法: 构建我们的测试对象fixture

  就像下面的形式:   #include "DiskDataTestCase.h"
  CPPUNIT_TEST_SUITE_REGISTRATION(DiskDataTestCase);
  void DiskDataTestCase::setUp()
  {
   fixture = new DiskData();
  }
  void DiskDataTestCase::tearDown()
  {
   delete fixture;
   fixture = NULL;
  }
  void DiskDataTestCase::loadTest()
  {
   // our load test logic
  }
  void DiskDataTestCase::storeTest()
  {
   // our store test logic
  }

  编写测试用例

  一旦我们知道我们要测什么之后,我们就可以写测试用例了。我们能够执行所有的我们需要的操作:使用普通库函数,第三方库,win32api库函数,或简单使用c++内部操作

  有时候,我们需要调用外部辅助文件或者数据库,比较外部文件和内部数据是否一致。

  每发现一个错误时9比如发现内部数据和外部数据不同我们就创建一个异常,使用 CPPUNIT_FAIL(message) 来显示异常信息。

检测一个条件就使用
  CPPUNIT_ASSERT(condition):如果为false就抛出异常

  CPPUNIT_ASSERT_MESSAGE(message, condition): 如果为false就抛出制定的信息。

  CPPUNIT_ASSERT_EQUAL(expected,current): 检测期望值

  CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,current): 当比较值不相等时候抛出的制定的信息。

  CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta): 带精度的比较

  下面是测试loadTest的例子,   //
  // These are correct values stored in auxiliar file
  //
  #define AUX_FILENAME "ok_data.dat"
  #define FILE_NUMBER 19
  #define FILE_STRING "this is correct text stored in auxiliar file"
  void DiskDataTestCase::loadTest()
  {
   // convert from relative to absolute path
   TCHAR absoluteFilename[MAX_PATH];
   DWORD size = MAX_PATH;
   strcpy(absoluteFilename, AUX_FILENAME);
   CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );
   // executes action
   CPPUNIT_ASSERT( fixture->load(absoluteFilename) );
   // ...and check results with assertions
   LPDATA loadedData = fixture->getData();
   CPPUNIT_ASSERT(loadedData != NULL);
   CPPUNIT_ASSERT_EQUAL(FILE_NUMBER, loadedData->number);
   CPPUNIT_ASSERT( 0 == strcmp(FILE_STRING,
   fixture->getData()->string) );
  }

  在这个case我们得到四个可能的错误:   load method's return value
  getData method's return value
  number structure member's value
  string structure member's value

  第二个用例也是相似的。但是困难点,我们需要使用已知的数据来填充fixture,把它存在磁盘临时文件里,然后打开两个文件(新的和辅助文件),读并比较内容,两者如一致就正确   void DiskDataTestCase::storeTest()
  {
   DATA d;
   DWORD tmpSize, auxSize;
   BYTE *tmpBuff, *auxBuff;
   TCHAR absoluteFilename[MAX_PATH];
   DWORD size = MAX_PATH;
   // configures structure with known data
   d.number = FILE_NUMBER;
   strcpy(d.string, FILE_STRING);
   // convert from relative to absolute path
   strcpy(absoluteFilename, AUX_FILENAME);
   CPPUNIT_ASSERT( RelativeToAbsolutePath(absoluteFilename, &size) );
   // executes action
   fixture->setData(&d);
   CPPUNIT_ASSERT( fixture->store("data.tmp") );
   // Read both files contents and check results
   // ReadAllFileInMemory is an auxiliar function which allocates a buffer
   // and save all file content inside it. Caller should release the buffer.
   tmpSize = ReadAllFileInMemory("data.tmp", tmpBuff);
   auxSize = ReadAllFileInMemory(absoluteFilename, auxBuff);
   // files must exist
   CPPUNIT_ASSERT_MESSAGE("New file doesn't exists?", tmpSize > 0);
   CPPUNIT_ASSERT_MESSAGE("Aux file doesn't exists?", auxSize > 0);
   // sizes must be valid
   CPPUNIT_ASSERT(tmpSize != 0xFFFFFFFF);
   CPPUNIT_ASSERT(auxSize != 0xFFFFFFFF);
   // buffers must be valid
   CPPUNIT_ASSERT(tmpBuff != NULL);
   CPPUNIT_ASSERT(auxBuff != NULL);
   // both file's sizes must be the same as DATA's size
   CPPUNIT_ASSERT_EQUAL((DWORD) sizeof(DATA), tmpSize);
   CPPUNIT_ASSERT_EQUAL(auxSize, tmpSize);
   // both files content must be the same
   CPPUNIT_ASSERT( 0 == memcmp(tmpBuff, auxBuff, sizeof(DATA)) );
   delete [] tmpBuff;
   delete [] auxBuff;
   ::DeleteFile("data.tmp");
  }

  调用用户接口

  最后,我们看看用一个mfc 对话框(TestRunner.dll)用来说明。

  我们需要在我们的初始化函数中做如下初始化   #include <cppunit/ui/mfc/TestRunner.h>
  #include <cppunit/extensions/TestFactoryRegistry.h>
  BOOL CMy_TestsApp::InitInstance()
  {
   ....
   // declare a test runner, fill it with our registered tests and run them
   CppUnit::MfcUi::TestRunner runner;
   runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );
   runner.run();
   return TRUE;
  }


  只要定义一个test的实例,然后注册所有用例,在跑case。

推荐人评论

单元测试:验证我们系统中的所有逻辑单元的验证行为(并不考虑其他单元的相互关系,比如其他的可以打成桩函数等。)

用户评论

正在载入评论列表...

是谁推荐了此篇文章

专家头像李哲
个人blog发送信息
李哲推荐的其他文章

热点新闻

热点评论

    精彩专题

    资源下载

    网站简介|广告服务|VIP资费标准|银行汇款帐号|网站地图|帮助|联系方式|诚聘英才|English|版权声明|问题报告

    北京创新乐知广告有限公司 版权所有, 京 ICP 证 070598 号
    世纪乐知(北京)网络技术有限公司 提供技术支持
    Copyright 2000-2008, CSDN.NET, All Rights Reserved
    GongshangLogo