可不可不要这么样徘徊在目光内 / 你会察觉到我根本寂寞难耐 / 即使千多百个深夜曾在梦境内 / 我有吻过你这毕竟并没存在 / 人声车声开始消和逝 / 无声挣扎有个情感奴隶 / 是我多么的想她 / 但我偏偏只得无尽叹谓 / 其实每次见你我也着迷 / 无奈你我各有角色范围 / 就算在寂寞梦内超出好友关系 / 唯在暗里爱你暗里着迷 / 无谓要你惹上各种问题 / 共我道别吧别让空虚使我越轨 /

用cpp做c++单元测试

上一篇 / 下一篇  2007-08-21 23:13:17 / 个人分类:测试代码

 介绍:
        在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。


TAG: 测试代码

 

评分:0

我来说两句

Open Toolbar