您的位置:软件测试 > 开源软件测试 > 开源单元测试工具 > cppUnit
测试驱动开发入门-CppUnit
作者:网络转载 发布时间:[ 2012/11/29 15:11:55 ] 推荐标签:

测试驱动开发是一个现在软件界流行的词汇之一,可是很多人还是不得其门而入。这篇文章想通过对于CppUnit的介绍,给予读者一个基本的映像。如果你熟知CppUnit的使用,请参阅我的另一篇文章:CppUnit代码简介 - 第一部分,核心类来获得对于CppUnit进一步的了解。

II. 测试驱动开发
要理解测试驱动开发,必须先理解测试。测试是通过对源代码的运行或者别的方式的检测来确定源代码之中是否含有已知或者未知的错误。所谓测试驱动开发,是在开发前根据对将要开发的程序的要求,先写好所有测试代码,并且在开发过程中不时地通过运行测试代码来获得所开发的代码与所要求的结果之间的差距。很多人可能会有疑问:既然我还没有开始写代码,我怎么能够写测试代码呢?这是因为,虽然我们还没有写出任何实现代码,但是我们可以根据我们对代码的要求从使用者的角度写出测试代码。事实上,在开发前写出测试代码,可以检测你的要求是不是完善和精确,因为如果你写不出测试代码,表示你的需求还不够清晰。
这篇文章通过一个文件状态操作类来展示测试驱动开发相对于普通开发方法的优势。

III. 文件状态操作类(FileStatus)需求
构造函数,接受一个const std::string&作为文件名参数。
DWORD getFileSize()函数,获取这个文件的长度。
bool fileExists()函数,获取这个文件是否存在。
void setFileModifyDate(FILETIME ft)函数,设定这个文件的修改日期。
FILETIME getFileModifyDate()函数,返回这个文件的修改日期。
std::string getFileName()函数,返回这个文件的名字。

IV. CppUnit简介
我们所进行的测试,某种意义上说,是一个或者多个函数。通过对这些函数的运行,我们可以检测我们是否有错误。假设我们要对构造函数和getFileName函数进行测试,这里面有一个很显然的不变式,是对一个FileStatus::getFileName函数的调用,应该与传给这个FileStatus对象的构造函数的参数相同。于是我们有这样一个函数:
bool testCtorAndGetFileName()
{
    const string fileName( "a.dat" );
    FileStatus status( fileName );
    return ( status.getFileName() == fileName );
}
我们只需要测试这个函数的返回值可以知道是否正确了。在CppUnit中,我们可以从TestCase派生出一个类,并且重载它的runTest函数。

class MyTestCase:public CPPUNIT_NS::TestCase
{
public:
    virtual void runTest()
    {
        const std::string fileName( "a.dat" );
        FileStatus status( fileName );
        CPPUNIT_ASSERT_EQUAL( status.getFileName(), fileName );
    }
};

CPPUNIT_ASSERT_EQUAL是一个宏,在它的两个参数不相等的时候,会抛出异常。所以,理论上说,我们可以通过:

MyTestCase m;
m.runTest();

来进行测试,如果有异常抛出,那么说明代码写错了。可是,这显然不方便,也不是我们使用CppUnit的初衷。下面我们给出完整的代码:

// UnitTest.cpp : Defines the entry point for the console application.
//

#include "CppUnit/TestCase.h"
#include "CppUnit/TestResult.h"
#include "CppUnit/TextOutputter.h"
#include "CppUnit/TestResultCollector.h"

#include
#include

class FileStatus
{
    std::string mFileName;
public:
    FileStatus( const std::string& fileName ):mFileName( fileName )
    {}
    std::string getFileName() const
    {
        return mFileName;
    }
};

class MyTestCase:public CPPUNIT_NS::TestCase
{
public:
    virtual void runTest()
    {
        const std::string fileName( "a.dat" );
        FileStatus status( fileName );
        CPPUNIT_ASSERT_EQUAL( status.getFileName(), fileName );
    }
};

int main()
{
    MyTestCase m;
    CPPUNIT_NS::TestResult r;
    CPPUNIT_NS::TestResultCollector result;
    r.addListener( &result );
    m.run( &r );
    CPPUNIT_NS::TextOutputter out( &result, std::cout );
    out.write();
    return 0;
}

这里我先说一下怎样运行这个程序。假设你的CppUnit版本是1.10.2,解压后,你会在src文件夹中,发现一个CppUnitLibraries.dsw,打开它,并且编译。你会在lib文件夹中,发现一些 lib和dll,我们的程序需要依赖当中的某些。接着,创建一个Console应用程序,假设我们仅使用Debug模式,在Project Settings中,把预编译选项(Precompiled Header)选成No,把CppUnit的include路径加入到Additional Include Directories中,并且把Code Generation改成Multi-threaded Debug Dll,接着把CppUnitD.lib加入到你的项目中去。后把我们的这个文件替换main.cpp。这个时候,可以编译运行了。
这个文件中,前面四行分别是CppUnit相应的头文件,在CppUnit中,通常某个类定义在用它的类名命名的头文件中。接着是我们的string和 iostream头文件。然后是我们类的一个简单实现,只实现了这个测试中有意义的功能。接下去是我们的TestCase的定义,CPPUNIT_NS是 CppUnit所在的名字空间。main中,TestResult其实是一个测试的控制器,你在调用TestCase的run时,需要提供一个 TestResult。run作为测试的进行方,会把测试中产生的信息发送给TestResult,而TestResult作为一个分发器,会把所收到的信息再转发给它的Listener。也是说,我简单的定义一个TestResult并且把它的指针传给TestCase::run,这个程序也能够编译通过并且正确运行,但是它不会有任何输出。TestResultCollector可以把测试输出的信息都收集起来,并且后通过 TextOutputter输出出来。在上述的例子中,你所获得的输出是:

OK (1 tests)

这说明我们一共进行了1个测试,并且都通过了。如果我们人为地把"return mFileName;"改成"return mFileName + 'a';"以制造一个错误,那么测试的结果会变成:

!!!FAILURES!!!
Test Results:
Run:  1   Failures: 1   Errors: 0


1) test:  (F) line: 31 c:unittestunittest.cpp
equality assertion failed
- Expected: a.data
- Actual  : a.dat

上一页12345下一页
软件测试工具 | 联系我们 | 投诉建议 | 诚聘英才 | 申请使用列表 | 网站地图
沪ICP备07036474 2003-2017 版权所有 上海泽众软件科技有限公司 Shanghai ZeZhong Software Co.,Ltd