在前面章节中详细介绍了单元测试的方法以及单元测试的环境,但是手工单元测试时,测试用例、结果判断都需要人工干预,并且在软件测试过程中非法分支会对源代码造成破坏,导致测试用例不可重用,这样效率会很低。所以我们需要使用自动化测试的方法来进行单元测试,即BVT(BuildVerification Test,创建确认测试)。
目前市场关于单元自动化测试的工具也较多,本书主要介绍CppUnit的使用。CppUnit是一个用C++语言实现的单元测试框架,属于XUnit 系列中的一员。CppUnit 采用了BTV 的理念,用于面向对象C++程序的单元测试的框架。它提供了一系列的头文件和静态库,采用CppUnit 所提供的单元测试框架,可以很方便地开发测试用例,并实现单元测试的“自测试”。一方面使用CppUnit 开发出来的测试用例是固化的,对同一个功能多次执行的测试用例是完全相同的,并实现了测试的自动化,在测试用例的执行过程中不需要测试人员干预;另一方面CppUnit采用断言来判断测试用例的执行结果,并可将执行结果写入一个文本文件中。
CppUnit 作为一个成熟的单元测试框架,具有以下特点:
(1)测试代码就是普通的C++代码,不需要学习新的测试脚本。
(2)在测试期间测试代码和源代码一起运行,一旦发现错误,可以及时增加测试用例不停地进行测试,有利于发现更多的问题,更好地保证代码的质量。
(3)由于框架成熟,测试过程中就可以将更多的精力放在“桩函数的统一”“可回归的单元测试”的目标上。
(4)由于C++类有私有、保护成员,一般的外部单元测试工具对于C++的支持都不是很好,但CppUnit 就是针对C++的,且采用了很直接的做法,只要在被测类的头文件中声明测试类是其友元,就可以在测试中得心应手。
(5)CppUnit 是开源代码,测试过程中可以增加它的功能。
CppUnit 单元测试框架中的主要概念如下:
被测试类(CUT):被测试的类,该类的一个实例对象或者类本身作为测试的对象。
被测试方法:被测试的类中的成员方法。
测试用例(test case):用来测试一个功能是否正确的一系列测试执行,是测试执行和统计的最基本单位。
测试工厂:一般给一个被测试类定义一个测试工厂,对该被测试类的被测试方法的测试用例和数据进行组装。
测试装置(test fixture):容纳多个测试方法以及相关测试数据的类,它可以为多个测试方法准备相关数据、测试上下文,进行公共的初始化和后处理。
测试套(test suite):相关测试用例的集合,测试套之间可以互相嵌套,即一个测试套可以包含一些测试用例,也可以包含其他测试套。一般来说,测试套和被测试的方法对应,并且需要把测试套定义为测试装置类。
测试方法(test method):以一个函数形式表现出来的独立的测试执行。每个测试用例对应一个测试方法,但又不完全等同于测试方法。比如进行继承类的测试,虽然父类和子类有完全相同的测试方法,但在父类和子类中对应于同样测试方法的测试用例是不相干的。
使用CppUnit 单元测试框架进行单元自动化测试的步骤如下:
第一步:编译CppUnit 静态库文件*.lib 和动态库文件*.dll。
CppUnit 提供了两套框架库:一个为静态的lib;一个为动态的dll。Cppunit 工程为静态lib,cppunit_dll 工程为动态dll 和lib。
进入CppUnit 程序包,打开src 文件,打开CppUnitLibraries.dsw 程序,在Microsoft Visual Studio6.0 编辑器中单击“工程”下拉菜单,先选择“设置活动工程”菜单项,将当前活动工程分别设置为cppunit 和cppunit_dll 并进行编译,生成的静态文件*.lib 和动态库文件*.dll 都会输出到lib 文件夹中。还有一个工程TestRunner 需要注意,编译该工程也会输出一个dll 文件,该dll 文件提供了一个基于GUI 方式的测试环境。
第二步:建立基于对话框的工程。
打开Microsoft Visual Studio 6.0 编辑器,在“文件”下拉菜单中选择“新建”菜单项,创建一个基于dialog 的工程,如图10-13所示,在新建对话框中设置好工程名(假设设置的工程名为cppunit)和存放位置,单击“确定”按钮。
图10-13 新建工程
弹出如图10-14 所示的对话框,选择“基本对话”单选项,并单击“完成”按钮。
图10-14 设置应用程序类型
第三步:屏蔽工程对话框。
在工程cppunit.cpp 工程文件中,找到BOOL CCppunitApp::InitInstance()方法,将如下代码注释掉:
/*CCppunitDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
/*TODO: Place code here to handle when the dialog is
dismissed with OK*/
}
else if (nResponse == IDCANCEL)
{
/* TODO: Place code here to handle when the dialog is
dismissed with Cancel */
}
*/
因为测试过程中需要调用CppUnit 自带的GUI 对话框(如图10-15 所示),所以需要将创建时生成的基于对话框的应用程序类型注释掉。
图10-15 TestRunner 对话框
第四步:实现CppUnit 测试执行器,并将测试工厂添加到测试执行器中。
找到BOOL CCppunitApp::InitInstance()方法,添加如下代码:
//添加CppUnit 的MFC 类型的测试执行器
CppUnit::MfcUi::TestRunner runner;
//为被测试类(这里是CCounter)定义一个测试工厂(这里取名叫CounterTest)
CppUnit::TestFactoryRegistry ®istry= CppUnit::TestFactoryRegistry::getRegistry("CounterTest");
//并将工厂添加到测试执行器中
runner.addTest( registry.makeTest() );
//运行执行器,显示执行器GUI 界面
runner.run();
由于在BOOL CCppunitApp::InitInstance()中引用了CppUnit 的类,所以在文件开始处要添加如下头文件:
#include
#include
第五步:添加被测对象。
选择菜单“工程”→“添加工程”→“文件”选项,将被测对象所在文件(*.h 和*.cpp)添加到工程中(假设添加的被测试对象为Iscodeline.cpp 文件)。
第六步:在工程中为被测对象Iscodeline 编写测试类文件CounterTest(可以自定义文件名)。
选择菜单“工程”→“添加工程”→“新建”选项,分别在源文件和头文件中新建CounterTest.cpp源文件和CounterTest.h 头文件,添加后如图10-16 所示。
图10-16 添加测试类文件
在CounterTest.h 头文件中添加如下代码:
#include "cppunit/extensions/HelperMacros.h"
class IsCodeLineTest : public CppUnit::TestFixture {
//声明一个TestSuite
CPPUNIT_TEST_SUITE( IsCodeLineTest);
//添加测试用例到TestSuite,定义新的测试用例需要在此声明
CPPUNIT_TEST( Counter_UT_IsCodeLine_001);
// TestSuite 声明完成
CPPUNIT_TEST_SUITE_END();
public:
//定义测试用例
void Counter_UT_IsCodeLine_001();
};
在CounterTest.cpp 源文件中添加如下代码:
#include "stdafx.h"
#include "CounterTest.h"
#include "Counter.h"
// 把这个TestSuite 注册到名为“CounterTest”的工厂中
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( IsCodeLineTest,"CounterTest" );
#define RET_OK 0
#define RET_FAIL 1
void IsCodeLineTest::Counter_UT_IsCodeLine_001()
{
//定义输入参数
int bIsComment;
CString szFileLine;
//定义期望输出
int iOkReturn;
int iOkIsComment;
//定义测试实际输出
int iResult;
CCounter m_counter;
//用例输入
szFileLine = "int a";
bIsComment = false;
//期望输出
iOkReturn = RET_OK;
iOkIsComment = false;
//驱动被测函数
iResult = m_counter.IsCodeLine(szFileLine,bIsComment);
//结果比较
CPPUNIT_ASSERT_EQUAL(iOkReturn,iResult);
CPPUNIT_ASSERT_EQUAL(iOkIsComment,bIsComment);
}
第七步:添加CppUnit 库文件。
选择菜单“工程”→“添加工程”→“文件”选项。把CppUnit 相关的lib 文件和dll 文件
(cppunitd.lib、cppunitd_dll.lib、testrunnerd.lib)加入到工程中。
第八步:设置头文件和lib 库文件路径,打开RTTI 开关,给dll 库设置环境变量。
单击“工具”下拉菜单,选择“选择”菜单项,在弹出的“选择”对话框中选择“目录”选项
卡,设置CppUnit 的include 文件路径和lib 文件路径,如图10-17 所示。
图10-17 设置include 文件路径和lib 文件路径单击“工程”下拉菜单,选择“设置”菜单项,在弹出的“工程设置”对话框中选择C/C++选项卡,在“分类”下拉列表中选择C++ Language 选项,选中“允许时间类型信息(RTTI)”复选框,如图10-18 所示。
TestRunnerd.dll 提供了基于GUI 的测试环境,为了让我们的测试程序能正确调用它,需要把TestRunnerd.dll 拷贝到工程路径下,或者在操作系统的环境变量Path 中添如TestRunnerd.dll 的路径。
第九步:编译执行。
编译连接成功后,运行测试,出现如图10-19 所示窗口,表示测试用例Test1 运行成功。
图10-18 开启RTTI 设置
图10-19 执行结果
本章详细介绍了单元测试的方法,包括单元测试的定义、单元测试的重点、单元测试的环境和单元测试的策略,其中单元测试的重点和单元测试的环境是重点内容;然后介绍了单元测试中常用的方法,包括静态测试技术和动态测试技术两个方面,这些方法是进行设计单元测试用例的基础,也是本章的核心内容;最后介绍了如何使用CppUnit 进行单元自动化测试。