UnitTest
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()中进入释放。
2TDD:测试驱动开发
定义:使用测试驱动开发。
步骤:
编写测试代码,如果失败,则进行生产代码开发,测试通过后,进行重构或者开始新的测试。
3CppUnit(推荐)
参考: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
Std::ofstream(文件路径);
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>
#pragma comment(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()函数
b) 将测试执行函数在InitialInstance()函数中实现。
详见3.3.3。
3.6.3.4 生成唯一的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()
{
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()
{
m_pRunner = new CppUnit::MfcTestRunner;
m_pRunner->addTest(CDrawGeometryTest::suite());
}
//view.cpp中的响应函数,点击时可运行
voidCDrawGeometryTestView::OnTestRun()
{
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:实型相等判断,需要给出限值。
4Google 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 添加测试用例
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 测试过程
所有的测试都是单独进行,相同的固件测试也会在每次测试之前和之后进行初始化和析构,不会对其它测试有任何影响。
5CppTest
Parasoft公司出品。特点是简单易用,收费。
安装完成后会在IDE中集成,操作简单,但是会影响原有工程,不符合测试不影响目标的原则。
卸载时会造成原有项目文件更改,启动时会报错,给目标项目带来不确定性。
5.1 安装
下载安装文件后,默认安装。
5.2 测试
单元测试可以使用两种方式,一种是使用测试套件(suit),这种方式,只能生成单元测试框架,需要手动添加测试用例。