4 测试过程
示例类定义如下:
class CMyClass
{
public:
//加法函数
int Add(int a,int b);
//计算空调制冷器运行时间
int WorkTime(int* pSecond);
};
加法函数Add()是入门示例,WorkTime()是接近应用的示例,功能是计算空调制冷器运行时间,需调用桩代码取得环境温度。测试过程,使用VC6.0。
假设CppUnit安装目录为D:/CppUnit/CppUnit,被测试文件MyClass.cpp和MyClass.h位于D:/CppUnit/Project目录。
1、用VC6.0建立一个一程,即测试工程。第一步左边选择MFC AppWizard(exe),工程名为TestProject,存放位置与Project并列,即存放于D:/CppUnit/TestProject。第二步选择Dialog based,点击Finish。这个示例使用了图形界面,所以要建立一个对话框工程。
2、把文件CppUnitPub.h和TestRun.cpp拷贝到D:/CppUnit/TestProject。
3、拷贝TestClassName.cpp和TestClassName.h文件到D:/CppUnit/TestProject,并重命名文件,将文件名中ClassName替换为实际类名。这里,被测试类名是CMyClass,因此,把文件名改为TestMyClass.cpp/.h。
用文本编辑工具如EditPlus打开这两个文件,进行以下替换:
TestClassName替换为TestMyClass。
ClassName替换为CMyClass。
TestHeaderName替换为TestMyClass.h。
HeaderName替换为MyClass.h。
4、将2、3拷贝和修过的文件、以及被测试类的文件(即D:/CppUnit/Project目录下的MyClass.cpp/.h)加入测试工程,方法是在VC6.0的Project菜单,单击Add files…。
5、设置头文件搜索目录,包括CppUnit,以及产品项目的头文件目录:VC6.0->Project->Setting->C++,Category选择Preprocessor,在Additional include directories中添加搜索目录:D:/CPPUNIT/CPPUNIT/include,D:/CPPUNIT/Project。
6、加入CppUnit的静态和动态库。VC6.0->Project->Setting->Link,Category选择Input,Application library path填写D:/CPPUNIT/CPPUNIT/lib。将CppUnit目录下的TestRunner.dll文件拷贝到D:/CppUnit/TestProject。
7、被测试函数WorkTime()调用了一个未实现的函数,要用桩来代替,后面再介绍桩代码编写,先把其中的代码行success = GetTemperature(&temperature);前加上注释符//。如果设定过程没有错误,这时应该可以通过编译。
8、在CTestProjectApp::InitInstance()函中的开头添加以下代码:
void TestRunAll();
TestRunAll();
return FALSE;
9、在TestRun.cpp文件的TestRunAll()函数中加入测试类:runner.addTest(TestMyClass::GetSuite());
10、编写CMyClass::Add()函数的测试函数,在TestMyClass.cpp文件中添加如下代码:
void TestCMyClass::testAdd()
{
CASE_BEGIN("");
int a = 1;
int b = 1;
int ret = pObj->Add(a,b);
CPPUNIT_ASSERT_EQUAL(2,ret);
CASE_END();
CASE_BEGIN("");
int a = 10;
int b = 10;
int ret = pObj->Add(a,b);
CPPUNIT_ASSERT_EQUAL(20,ret);
CASE_END();
}
在TestMyClass.h文件中添加测试函数声明void testAdd();并在CPPUNIT_TEST_SUITE宏内添加注册代码CPPUNIT_TEST(testAdd);
编译并运行后会显示CppUnit的图形界面,点击“Browse”,选择要执行的测试,再点击“Run”,即会显示测试结果。由于CMyClass::Add()函数中把+写成了-,所以测试会失败,修改后测试通过。
11、编写CMyClass::WorkTime()函数的测试函数,先把7所添加的注释符删除。这个函数首先调用int GetTemperature(int* pTemperature)函数取得环境温度,然后根据环境温度和预设的目标温度的差,计算制冷器的运行时间。在TestMyClass.cpp添加测试函数:
extern int gExpectTemperature;
void TestMyClass::testWorkTime()
{
CASE_BEGIN("Failed");
gExpectTemperature = 25;
int second = 0;
int ret = pObj->WorkTime(&second);
CPPUNIT_ASSERT_EQUAL(0,ret);
CPPUNIT_ASSERT_EQUAL(0,second);
CASE_END();
CASE_BEGIN("ok-23");
gExpectTemperature = 25;
int second = 0;
int ret = pObj->WorkTime(&second);
CPPUNIT_ASSERT_EQUAL(0,ret);
CPPUNIT_ASSERT_EQUAL(0,second);
CASE_END();
CASE_BEGIN("ok-28");
gExpectTemperature = 25;
int second = 0;
int ret = pObj->WorkTime(&second);
CPPUNIT_ASSERT_EQUAL(1,ret);
CPPUNIT_ASSERT_EQUAL(180,second);
CASE_END();
CASE_BEGIN("ok-25");
gExpectTemperature = 25;
int second = 0;
int ret = pObj->WorkTime(&second);
CPPUNIT_ASSERT_EQUAL(0,second);
CASE_END();
}
在TestMyClass.h文件中添加测试函数声明void testWorkTime ();并在CPPUNIT_TEST_SUITE宏内添加注册代码CPPUNIT_TEST(testWorkTime);
现在编译还无法通过,因为GetTemperature()未实现。假设我们对GetTemperature()函数的测试需求如下表,要使用例与桩输出匹配,如何编写桩代码呢?可以用命名法来匹配用例与桩输出。
用例 |
返回值 |
出参(环境温度) |
1 |
0(取环境温度失败) |
|
2 |
1(取环境温度成功) |
23 |
3 |
1 |
25 |
4 |
1 |
28 |
在测试代码中,每个用例的第一行可以给用例命名,如CASE_BEGIN("Failed"),表示取环境温度失败,CASE_BEGIN("ok-23"),表示取环境温度成功,值为23。
在测试工程目录下新建文件Stub.cpp,并加入测试工程,编写桩代码,如下:
#include "stdafx.h"
#include "CppUnitPub.h"
int GetTemperature(int* pTemperature)
{
if(caseNameIs("Failed"))
return 0;
if(caseNameIs("ok-23"))
{
*pTemperature = 23;
return 1;
}
if(caseNameIs("ok-25"))
{
*pTemperature = 25;
return 1;
}
if(caseNameIs("ok-28"))
{
*pTemperature = 28;
return 1;
}
return 0;
}
桩代码调用caseNameIs()来判断当前用例名,并输出合适的值。
本例中,如果GetTemperature()调用的是实际代码,在测试时称为不可控,即无法控制真实的环境温度使其满足测试的需要;如果调用的是桩代码,且桩代码只是空函数,则称为失真,简单的失真可以用命名法解决,但不能解决复杂的失真,例如,GetTemperature()在同一用例中被多次调用,每次要求输出不同;或者桩与被测函数的关系是多对多,这就难于维护桩输出与用例的关系了。另外,编写桩代码也不能解决不可控,假如GetTemperature()也是CMyClass类的成员函数,在不修改产品代码的前提下,无法调用桩。这些问题,就不是开源框架所能解决的了。