单元测试UnitTest

来源:互联网 发布:软件破损怎么处理 编辑:程序博客网 时间:2024/05/19 03:42

UnitTest

sf2gis@163.com

2013年5月20日

 

1  单元测试

定义:基于某种单元测试框架编写的测试代码,用于测试特定函数或类,验证其逻辑行为的假设,具有全自动,可信赖,可维护,可读性强、运行快速的特点。

单元测试属性白盒测试和结构性测试,一般由开发人员进行。主要由收费的CPPTest和开源的CPPUnit。

单元测试框架主要进行单元测试管理和自动化测试。

1.1 测试的原则

任何包含逻辑的代码,都应该被测试。

每一个目标项目建立测试项目,每一个目标类建立一个测试类,每一个目标方法建立至少一个测试方法。

1.2 测试的要求

测试不能影响目标程序。

1.3 测试用例命名

参考:http://book.51cto.com/art/201111/301856.htm 《单元测试的艺术》

1.4 测试构造与析构函数

setUp():每次测试函数执行前执行,用于测试构造。

tearDown():每次测试函数执行结束后执行,用于测试析构。

如果要测试一个类的功能,可以将类设置为数据成员,在setUp()中进行初始化,在tearDown()中进入释放。

2 TDD:测试驱动开发

定义:使用测试驱动开发。

步骤:

编写测试代码,如果失败,则进行生产代码开发,测试通过后,进行重构或者开始新的测试。

3 CppUnit(推荐)

参考:http://www.cnblogs.com/eric_lgf/archive/2009/10/10/1580330.html

CPPUnit移植于JUnit,源于极限编程思想。

CppUnit中的测试方法要求所有的测试函数都返回void,不接受参数。

3.1 下载

SVN:https://cppunit.svn.sourceforge.net/svnroot/cppunit/trunk/cppunit/

Code:http://sourceforge.net/projects/cppunit/

目前版本是1.12.1

3.2 编译

3.2.1 转换

解压后用VS打开src下的.dsw(如果不是vc6,需要转换,默认转换就可以了)。

3.2.2 编译

在VS2010下,会有6个工程,其中CPPUnit为静态库,CPPUnit_Dll为动态库。其它的都是插件,如果在Console下使用,则只编译CPPUnit(lib类型)就可以了。如果要在MFC下使用,还要编辑TestRunner(dll类型),用于图形化显示测试结果。

3.2.2.1  修改输出文件名

CPPUnit在编译时会有输出文件与期望不同的警告并报错,这里根据提示改为工程名+d。

TestRunner在编译时也存在输出名称的警告,这里也根据提示改为工程名+d。

3.2.2.2  修改MSC版本

还会有一个错误,在src\msvc6\testrunner\MsDevCallerListCtrl.cpp下67行有个MSC的版本,将7改为8。

3.2.2.3  修改字符集

MFC默认使用Unicode,而CPPUnit使用Mulitbyte,在新建测试项目时,如果字符集不同,会报错。这里在编译的时候选择字符集为Unicode,可以避免每次新建测试项目时设置字符集。

3.3 在目标工程中测试

在目标工程中测试会导致修改目标工程,不符合测试不影响目标的要求,这里仅用来检测CppUnit的可用性。

3.3.1 构建测试类

3.3.1.1  添加库及头文件

测试类用于测试目标类的服务,派生于CPPUNIT_NS::TestFixture,用于生成测试套件(SUITE)。所以需要包含目标类头文件和cppunit.lib库文件。

3.3.1.2  添加测试套件

构建测试类时,首先要添加测试套件,使用宏来定义,这里需要一个定义宏的头文件cppunit\extensions\helpmacros.h。

测试套件格式

CPPUNIT_TEST_SUITE(测试类名称);

CPPUNIT_TEST(测试函数名);

……

CPPUNIT_TEST_SUITE_END();

3.3.1.3  实现测试构造、析构函数(setUp(),tearDown())

setUp():每次测试函数执行前执行,用于测试构造。

tearDown():每次测试函数执行结束后执行,用于测试析构。

3.3.2 实现测试函数

实现每个测试函数,并添加断言。

3.3.3 执行测试

3.3.3.1  构建测试执行者

测试执行者在CPPUnit中由类CppUnit::TextUi::TestRunner实现。

声明位于cppunit/ui/text/testrunner.h中。

首先生成一个测试执行者TestRunner的对象。

3.3.3.2  向执行者添加测试套件

向测试执行者中添加测试套件。

objTestRunner.addTest(测试类::suite());

3.3.3.3  输出

如果需要将测试结果输出到文件中,可以在这里设置输出文件,并输出。默认情况下输出到命令行窗口。

输出文件头文件:cppunit/compileroutputter.h

a)   生成输出文件

Std::ofstream(文件路径);

b)   生成输出对象

CppUnit::CompilerOutputter*pOutputter = CppUnit::CompilerOutputter::defaultOutputter(&result,输出文件);

c)   将输出对象的指针设置到测试执行者对象。

objTestRunner.setOutputter(pOutputter);

3.3.3.4  执行测试

调用测试执行函数,就可以进行自动测试了。

Enjoy!

3.3.4 示例

//calctests.h

//用于测试CCalc

#pragma once

#include "Calc.h"

#include <cppunit\extensions\HelperMacros.h>

#pragma comment(lib,"cppunitd.lib")

class CCalcTests:public CPPUNIT_NS::TestFixture

{

    CPPUNIT_TEST_SUITE(CCalcTests);

    CPPUNIT_TEST(Sum_ValidAdd_Return0);

    CPPUNIT_TEST(Sum_ValidAdd_Return5);

    CPPUNIT_TEST_SUITE_END();

public:

    CCalcTests(void);

    ~CCalcTests(void);

private:

    CCalc* m_pCCalc;

public:

    virtualvoid setUp(void);

    virtualvoid tearDown(void);

    voidSum_ValidAdd_Return0(void);

    voidSum_ValidAdd_Return5(void);

 

};

 

//calctests.cpp

#include "CalcTests.h"

CCalcTests::CCalcTests(void)

{

}

CCalcTests::~CCalcTests(void)

{

}

void CCalcTests::setUp(void)

{

    m_pCCalc = new CCalc();

}

void CCalcTests::tearDown(void)

{

    deletem_pCCalc;

}

void CCalcTests::Sum_ValidAdd_Return0(void)

{

    int_Return = m_pCCalc->sum(0,0);

    CPPUNIT_ASSERT_EQUAL(0,_Return);

}

void CCalcTests::Sum_ValidAdd_Return5(void)

{

    int_Return = m_pCCalc->sum(2,3);

    CPPUNIT_ASSERT_EQUAL(5,_Return);

}

 

//test.cpp

#include <cstdio>

#include <cppunit\ui\text\TestRunner.h>//测试执行者

#include <cppunit\CompilerOutputter.h>//用于输出到文件

#include "CalcTests.h"

//执行测试函数

void testUtility()

{

    CppUnit::TextUi::TestRunner objTestRunner;//测试执行者对象

    objTestRunner.addTest(CCalcTests::suite());//添加测试套件

    //输出到文件

    std::ofstream fout("./testlog.log");//输出文件

    CppUnit::CompilerOutputter*pOutputter =CppUnit::CompilerOutputter::defaultOutputter(&objTestRunner.result(),fout);//输出对象

    objTestRunner.setOutputter(pOutputter);//设置输出对象

    objTestRunner.run();//执行测试

}

 

int main(int argc,char**argv)

{

    testUtility();

    getchar();

    return0;

}

3.4 独立工程测试

3.4.1 构建测试类

在构建测试类中,由于测试类与目标类不在同一个工程中,但是测试类要使用目标类进行测试,所以,在构建测试类之前,要将目标类的实现文件添加到测试类中(添加现有项,引导进入目标类的工程文件夹中,添加目标类的实现文件)。

在使用目标类的地方要添加目标类原头文件(include的路径指向目标类的工程文件夹)。

3.4.2 实现测试函数

同3.3.2

3.4.3 执行测试

同3.3.3

3.4.4 示例

    //targettests.h

#pragma once

#include <cppunit\extensions\HelperMacros.h>

#pragma comment(lib,"cppunitd.lib")

class CTarget;

class CTargetTests:publicCPPUNIT_NS::TestFixture

{

    //testmacro

    CPPUNIT_TEST_SUITE(CTargetTests);

    CPPUNIT_TEST(sum_ValidSum_0);

    CPPUNIT_TEST_SUITE_END();

public:

    CTargetTests(void);

    ~CTargetTests(void);

    voidsum_ValidSum_0(void);

    virtualvoid setUp(void);

    virtualvoid tearDown(void);

private:

    CTarget *m_pTarget;

};

 

    //targettests.cpp

#include "TargetTests.h"

#include "..\target\Target.h"

CTargetTests::CTargetTests(void)

    : m_pTarget(NULL)

{

}

 

 

CTargetTests::~CTargetTests(void)

{

}

 

 

void CTargetTests::sum_ValidSum_0(void)

{

    CPPUNIT_ASSERT_EQUAL(1,1);

}

 

 

void CTargetTests::setUp(void)

{

    m_pTarget = new CTarget;

}

 

 

void CTargetTests::tearDown(void)

{

    deletem_pTarget;

    m_pTarget = NULL;

}

 

//main.cpp

#include<cstdio>

#include"TargetTests.h"

#include <cppunit\ui\text\TestRunner.h>

void testUtility()

{

    CppUnit::TextUi::TestRunnerobjTestRunner;

    objTestRunner.addTest(CTargetTests::suite());

    objTestRunner.run();

}

int main(int argc,char**argv)

{

    testUtility();

    getchar();

    return0;

}

 

 

3.5 MFC测试

如果工程是带有界面的MFC工程,则使用textoutter仍然可以进行测试,但将无法显示输出的内容,当然,也可以输出到文件进行查看。

如果在带有界面的工程中测试呢,这里使用MFC界面测试。

使用带有界面的测试要使用到cppunitd..lib和testrunner.dll。这里增加了testrunner工程。在cppunit源文件中编译testrunner(详见3.2.2)。

3.5.1 构建测试类

同3.4.1

3.5.2 实现测试函数

同3.3.2

3.5.3 执行测试

3.5.3.1  执行测试时,由于要使用界面,所以这里要添加对testrunner.dll的引用

a)   cppunit源文件编译获得testrunnerd.dll和testrunnerd.lib。

b)   将dll文件复制到测试工程的目录中。

c)   添加lib引用。

3.5.3.2  在App类中添加MFCRunner的执行

一定要将MFCRunner的执行放在初始之后的第一行,如果执行了其它的操作(如显示其它对话框等),会出现错误。

在其后可以添加其它的内容。

3.5.4 示例

// MfcTargetTests.cpp : 定义应用程序的类行为。

//

 

#include "stdafx.h"

#include "MfcTargetTests.h"

#include "MfcTargetTestsDlg.h"

#include"TargetTests.h"

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

#pragmacomment(lib,"testrunnerd.lib")

 

……

BOOL CMfcTargetTestsApp::InitInstance()

{

    // 如果一个运行在 Windows XP上的应用程序清单指定要

    // 使用 ComCtl32.dll版本 6 或更高版本来启用可视化方式,

    //则需要InitCommonControlsEx()。否则,将无法创建窗口。

    INITCOMMONCONTROLSEX InitCtrls;

    InitCtrls.dwSize = sizeof(InitCtrls);

    // 将它设置为包括所有要在应用程序中使用的

    // 公共控件类。

    InitCtrls.dwICC =ICC_WIN95_CLASSES;

    InitCommonControlsEx(&InitCtrls);

 

    CWinApp::InitInstance();

 

 

    AfxEnableControlContainer();

 

    // 创建 shell管理器,以防对话框包含

    // 任何 shell树视图控件或 shell 列表视图控件。

    CShellManager *pShellManager = new CShellManager;

 

    // 标准初始化

    // 如果未使用这些功能并希望减小

    // 最终可执行文件的大小,则应移除下列

    // 不需要的特定初始化例程

    // 更改用于存储设置的注册表项

    //TODO: 应适当修改该字符串,

    // 例如修改为公司或组织名

    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));

    CppUnit::MfcUi::TestRunner objRunner;

    objRunner.addTest(CTargetTests::suite());

    objRunner.run();

 

 

    CMfcTargetTestsDlg dlg;

    m_pMainWnd = &dlg;

    INT_PTR nResponse =dlg.DoModal();

    if(nResponse == IDOK)

    {

        //TODO: 在此放置处理何时用

        //  “确定来关闭对话框的代码

    }

    elseif (nResponse == IDCANCEL)

    {

        //TODO: 在此放置处理何时用

        //  “取消来关闭对话框的代码

    }

 

    // 删除上面创建的 shell管理器。

    if(pShellManager != NULL)

    {

        deletepShellManager;

    }

 

    // 由于对话框已关闭,所以将返回 FALSE以便退出应用程序,

    //  而不是启动应用程序的消息泵。

    returnFALSE;

}

 

3.6 MFC界面测试纯C++程序

使用MFC测试类测试非MFC程序时,会提示向目标程序中添加stdafx.h。测试需要遵守不影响目标程序的原则,所以一般来说使用MFC测试MFC程序,console测试console程序。

如果需要使用界面测试Console程序,可以通过构建一个带界面的win32应用程序,构建一个App类,来运行测试界面。

3.6.1 构建测试类

同3.4.1

3.6.2 实现测试函数

同3.3.2

3.6.3 构建App类

3.6.3.1  引用MFC

使用CPPUnit的MFC界面,需要添加MFC引用。

注意设置字符集与TestRunner编译时使用的字符集相同,否则会报错。

3.6.3.2  创建派生于CWinApp的类

CWinApp位于afxwin.h头文件中。

3.6.3.3  实现InitialInstance()函数

a)   首先调用基类函数;

b)   将测试执行函数在InitialInstance()函数中实现。

详见3.3.3。

3.6.3.4  生成唯一的app对象

在APP的实现文件中生成一个唯一的对象,作为全局唯一对象。

3.6.4 执行测试

F5,Enjoy!

3.6.5 示例

//targettestsapp.h

#pragma once

class CTargetTestsApp :

    publicCWinApp

{

public:

    CTargetTestsApp(void);

    virtual~CTargetTestsApp(void);

    virtualBOOL InitInstance();

};

 

//targettestsapp.cpp

#include <afxwin.h>

#include "TargetTestsApp.h"

#include "TargetTests.h"

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

#pragma comment(lib,"testrunnerd.lib")

CTargetTestsApp theApp;

CTargetTestsApp::CTargetTestsApp(void)

{

}

 

 

CTargetTestsApp::~CTargetTestsApp(void)

{

}

 

 

BOOL CTargetTestsApp::InitInstance()

{

    //TODO: 在此添加专用代码和/或调用基类

    CWinApp::InitInstance();

    CppUnit::MfcUi::TestRunnerobjRunner;

    objRunner.addTest(CTargetTests::suite());

    objRunner.run();

    returnFALSE;

}

3.7 MFC界面测试具有绘图功能的程序

具有绘图功能的程序在测试时,不只要测试输出的结果是否正确,还要测试图形的绘制是否正确。因此应当在测试功能的同时,观察绘制结果。

@remarks此段内容仍不完善,需要添加可以自动清屏的功能。

3.7.1 构建测试类

同3.4.1

这是要注意的是,由于绘图需要使用界面的绘制工具DC,所以在测试类中要添加一个静态成员m_pDC

3.7.2 实现测试函数

同3.3.2

3.7.3 在Doc中添加测试界面Runner成员

这里要注意在头文件中MFCTestRunner类的声明使用Namespace:

namespace CppUnit

{

  class MfcTestRunner;

};

这里还要添加一个函数,当View要求运行Runner时调用。将DC以参数传过来。

3.7.4 在View中设置DC并运行Runner

在View中添加Menu来响应运行。每次运行前,可先清空屏幕。

3.7.5 示例

//doc.h

namespace CppUnit

{

    classMfcTestRunner;

};

 

class CDrawGeometryTestDoc : public CDocument

{

private:

    CppUnit::MfcTestRunner*m_pRunner;

public:

    voidrunTest(CDC **pDC);

 

//doc.cpp

CDrawGeometryTestDoc::CDrawGeometryTestDoc()

{

    //TODO: 在此添加一次性构造代码

    m_pRunner = new CppUnit::MfcTestRunner;

    m_pRunner->addTest(CDrawGeometryTest::suite());

 

}

 

 

//view.cpp中的响应函数,点击时可运行

    voidCDrawGeometryTestView::OnTestRun()

    {

        //TODO: 在此添加命令处理程序代码

        CDrawGeometryTestDoc* pDoc =GetDocument();

        ASSERT_VALID(pDoc);

        if(!pDoc)

            return;

 

        Invalidate(TRUE);

        CDC *pDC = GetDC();

        pDoc->runTest(&pDC);

 

    }

//test.h

            staticCDC *m_pDC;

//test.cpp

CDC * CDrawGeometryTest::m_pDC = NULL;

 

 

 

3.8 编译完成后自动执行测试

在目标程序的工程属性中,生成事件-》后期生成事件-》命令行中添加编译完成后需要执行的命令。这里添加测试程序的地址就可以了。

 

3.9 输出

在输出中,使用ui\text\runner测试无界面或文件型输出,在MFC中无法输出。

使用ui\mfc\runner测试MFC。

 

3.10 断言

CPPUNIT_ASSERT*(condition):断言宏,用于判断condition内容是否为真,如果为真,则测试成功,如果为假,则测试失败。

*为各种判断条件。

空:直接判断条件的真假。

_MESSAGE:如果测试失败则输出Message。

_EQUAL:相等判断。

_DOUBLES_EQUAL:实型相等判断,需要给出限值。

 

4 Google Test

参考:http://www.cfanz.cn/index.php?c=article&a=read&id=24867

4.1 下载

下载googletest源码,在msvc目录下编译。

地址:http://code.google.com/p/googletest/,目前版本1.6

4.2 编译

编译时要注意选择CRT运行时库版本。Gtest与工程的CRT版本要一致。这里在编译时可以预先生成几个常用的版本,用的时候直接从生成目录中拷贝。

Gtest默认是MT,而MFC多用MDd,所以要更改这里的版本。

 

4.3 测试

4.3.1 添加依赖库和include头文件

在MSVC目录下,找到生成的lib,将lib文件和include文件包含到当前项目中就可以使用。

4.3.2 初始化gtest并运行测试

4.3.2.1  添加静态库gtest.lib(调试版本为gtestd.lib)

这里注意与项目的crt库要一致。

4.3.2.2  添加gtest.h头文件

4.3.2.3  运行初始化函数

这里要注意参数的类型转化。

4.3.2.4  运行全部测试函数

4.3.3 添加测试用例

4.3.3.1  添加测试用例

添加gtest.h头文件,使用TEST宏,定义用例。

4.3.4 测试,查看测试结果

F5,Enjoy!

4.4 TEST与TEST_F

参考:http://longzxr.blog.sohu.com/205801950.html

4.4.1 TEST

用于简单测试,格式TEST(test_case_name,test_name);

Test_case_name:指测试案例名称;是测试名称的上一级。一个案例中可以包含任意数量的测试。用于分组。

Test_name:指测试名称,是案例中具体的一个测试名称;

测试案例名称+测试名称共同构成一个测试的全名。

 

4.4.2 TEST_F

Test_Fixtures:测试固件,与TestSuite相同。当多个测试使用同样的测试数据时,可以使用测试固件简化测试过程。

格式:TEST_F(test_case_name,test_name);

其中test_case_name必须是一个继承自testing::Test的子类,这个类将被作为内部类添加到测试中。

测试步骤如下:

4.4.2.1  添加一个继承自testing::Test的子类;

4.4.2.2  为类定义需要使用的数据和方法

4.4.2.3  如有必要,可以编写SetUp(),TearDown()函数执行构造和析构。

4.5 断言列表

ASSERT_*和EXPECT_*是主要使用的两个断言。

*是针对不同的数据类型设定的断言,包括:

TRUE/FALSE:boolean型。

EQ(==)、NE(!=)、LT(<)、LE(<=)、GT(>)、GE(>=):数字型。

STREQ、STRNE、STRCASEEQ、STRCASENE(忽略大小写):字符串型。

THROW(指定类型的异常),ANY_THROW(任意类型的异常),NO_THROW(无异常):异常。

PREDn(n指参数的个数):带参数的boolean型检测。

FLOAT_EQ,DOUBLE_EQ:浮点型近似相等。

NEAR:给点限值判定。

HRESULT_SUCCEEDED、HRESULT_FAILED:HRESULT。

 

4.6 测试过程

所有的测试都是单独进行,相同的固件测试也会在每次测试之前和之后进行初始化和析构,不会对其它测试有任何影响。

 

5 CppTest

Parasoft公司出品。特点是简单易用,收费。

安装完成后会在IDE中集成,操作简单,但是会影响原有工程,不符合测试不影响目标的原则。

卸载时会造成原有项目文件更改,启动时会报错,给目标项目带来不确定性。

5.1 安装

下载安装文件后,默认安装。

5.2 测试

单元测试可以使用两种方式,一种是使用测试套件(suit),这种方式,只能生成单元测试框架,需要手动添加测试用例。

6 CRT版本

参见VS配置.docx

 

另一种常用的方法是直接生成测试测试用例,这种方式下会生成测试套件以及所有的函数和类的测试用例。

 

0 0
原创粉丝点击