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