姚红+李茂斌
摘要:在面向对象程序设计中,动态多态性的引入为程序的可复用性和可扩充性提供了极大的便利,但同时也为程序的测试引入了一般测试方法无法很好应对的难题。在该文中,作者基于实践,提出了一种针对面向对象动态多态性的测试方法。该方法从静态分析入手,首先从被测程序中识别出需要进行动态多态性测试的代码,以此建立被测程序的动态多态性测试表,然后对该测试表进行分析,并最终在此基础上生成测试用例。该方法主要针对面向对象动态多态性的测试,可以作为一般面向对象软件测试的补充。
关键词:面向对象;动态多态性;软件测试
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2017)06-0138-02
1 概述
多态性是面向对象的重要特征,其与封装性和继承性并称为面向对象程序设计的三大特性。多态性的定义为:指允许不同的类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采取多种不同的行为方式。简单来说就是“一个对外的接口,有多个内在的实现方法”。多态性按运行时刻可划分为静态多态性和动态多态性。静态多态性是指定义有一个类的同名函数,它们根据参数的类型和个数不同进行语义区别,其主要通过函数的重载(Overload)和运算符重载来实现的,这种多态性在程序编译时系统就能识别调用的是哪个函数,也叫做静态绑定。动态多态性是指在程序执行前,系统无法根据函数名和参数来确定要调用哪个函数,必须在程序执行过程中,根据执行的具体情况来动态的确定,它是通过类的继承关系和虚函数重写(Override)来实现的,这种多态性在程序编译时系统无法识别调用的是哪个函数,直到运行时刻调用时才能确定,故又叫做动态绑定。
在对具有多态性的程序进行测试时,静态多态性的代码我们可以理解为多个名字相同的类成员函数,使用一般的测试方法就可以对其进行测试。相对于静态多态性,对动态多态性的程序代码进行测试则显得困难了许多,其中最主要的难点是测试用例的设计,由于动态多态性是在程序执行时才确定具体哪个函数被调用,所以这要求测试人员在设计测试用例时要用模拟程序运行的动态思维来考虑,才能使所有可能的函数调用均被测试用例覆盖,这无疑是一个很高的要求。
本人在多个工程项目软件测试经验教训基础上,参考了多种面向对象测试策略,提出了一种针对面向对象动态多态性的测试方法,这种方法从静态分析入手,首先从被测程序中识别出需要进行动态多态性测试的代码,以此建立被测程序的动态多态性测试表,然后对该测试表进行分析,并最终在此基础上生成测试用例。这种方法能很好地解决动态多态性代码的测试问题。
2静态分析
2.1 动态多态性识别
要在诸多程序代码中识别出哪部分代码需要进行动态多态性测试,就要对动态多态性的形成机制有深刻的理解。简单地说,动态多态性的形成需要三个基本条件:继承、虚函数重写和指向子类对象的父类指针,其中继承是动态多态性的基础,只有有继承关系的父类和子类之间才有可能形成动态多态性;虚函数重写是指父类定义实现一个虚函数接口,子类继承父类的接口并对该虚函数进行重写,这是动态多态性形成的技术关键;指向子类对象的父类指针是指将一个子类的对象赋予一个父类的指针,并使用该指针调用重写的虚函数,这样在程序运行时系统就会调用指针指向的虚函数表所记录的子类的函数,而不是父类的函数。
下面以一段代码来说明如何识别程序代码中的动态多态性。
class A {
virtual void fun1(){...}; //此处省略处理描述
virtual void fun2(){...};
};
class B : public A {
void fun1(){...}; //重写父类的虚函数fun1
void fun2(){...}; //重写父类的虚函数fun2
};
class C : public A {
void fun2(){...}; //重写父类的虚函数fun2
};
void Testfun(int i_flg)
{
A *a;
switch(i_flg) {
case 1:
a = new B; break;
case 2:
a = new C; break;
default:
a = new A; break;
}
a->fun1();
a->fun2();
}
由以上代码我们可以看出类A为父类,类B和类C为类A的子类,并分别重写了父类A的虚函数,在函数Testfun(int i_flg)代码的最后使用了父类指针完成虚函数的调用,具备了动态多态性的三个基本条件,那这段代码应该如何识别分析呢?经过简单分析我们不难发现,当输入变量i_flg的值为1时,指针a指向的为类B的对象,此时a->fun1()和a->fun2()调用均为类B的fun1和fun2调用,产生了动态多态性;当i_flg的值为2时,指针a指向的為类C的对象,但是情况有点复杂,由于类C没有重写父类的虚函数fun1,故a->fun1()执行的是类A的fun1调用,而a->fun2()则为类C的fun2调用,部分产生了动态多态性;当i_flg的值为1和2之外的值时,指针a指向的为类A的对象,此时a->fun1()和a->fun2()均为类A的函数调用,没有产生动态多态性。在实际测试时,被测代码肯定比以上示例代码要复杂得多,但动态多态性识别的思路和方法是基本一致的。
2.2动态多态性测试表的建立和分析
在对被测程序代码进行动态多态性识别时,需要同步进行的工作就是建立动态多态性测试表。测试表的作用为对动态多态性识别结果的记录,为下一步测试表分析和生成测试用例提供支撑。测试表记录的内容可以由测试人员根据项目实际情况自由裁定,但应该至少包括以下几条内容:
1)产生位置。该条用于记录和定位要进行动态多态性测试的代码段,比如:test.cpp/testfun(int i_flg)。
2)先决条件和影响因素。该条用于记录动态多态性产生的各种前提条件和影响因素,其作用为在设计测试用例时可作为测试的约束和输入。
3)表征代码和期望执行代码。表征代码即为产生动态多态性的源程序代码,如以上示例代码中的a->fun1()和a->fun2()(记录时指针a前应加上 A *以标识其类型,以免测试表项增多以后产生混淆),期望执行代码为表征代码在程序运行时应该执行的代码,如a->fun1()在i_flg为1时的期望执行代码为B::fun1()。
在完成动态多态性测试表的建立后,下一步的工作就是对测试表进行分析。测试表的分析工作就是对测试表项按其复杂性进行整理、分解或合并的过程,以期测试人员能根据它更方便的设计测试用例。测试表分析的理想结果是测试人员根据该表设计出较少的测试用例完成动态多态性测试的最大覆盖。当然,当建立起的测试表项较少或影响动态多态性的因素较少比较容易设计测试用例时,测试人员可以跳过测试表分析直接开始测试用例设计。
3 测试设计
对动态多态性的测试设计包括测试用例设计和测试程序设计。在经过前文介绍的动态多态性识别、动态多态性测试表的建立和分析一系列工作后,测试用例的设计就变得简单了:由测试表中的“先决条件和影响因素”一条细化、分解、加工即可生成测试用例的测试准备和测试输入;根据测试表中的“表征代码和期望执行代码”一条可以设计测试用例中各种分支下的期望执行过程和期望结果。至此,一个动态多态性测试用例的基本要素均已具备,接下来要做的就是将测试用例文档化并列入单元或集成测试说明的相应章节中,在被测程序进行类测试或类集成测试时一并进行测试。
测试程序的设计一般包括测试驱动、测试桩和辅助测试模块的设计。测试驱动和测试桩在一般的软件测试中均会用到,对于动态多态性的代码也无太大区别,此处就不再多做描述。对于辅助测试模块的设计,在这里简单介绍一下本人在进行动态多态性测试时常用的一种辅助手段—-动态窗口的设计方法,动态窗口的设计思想就是给被测类开一个窗口,在不破坏类封装性的前提下能方便的观察在测试过程中当前类的类型、属性及状态等信息。其实现方式就是在父类中定义用作窗口的虚函数,并在子类继承并重写该窗口函数,这样这个窗口就具有了动态多态性。动态窗口在测试过程中能提供很大的帮助,它能帮助测试人员方便地监视测试时执行过程和类的状态改变是否与预期的一致。值得注意的是在窗口函数编写过程中应遵循一个原则,就是窗口函数是一个观察窗口,它不能有任何会改变类属性和状态的操作,也不可与类的方法有嵌套条用关系,这对被测程序保持其代码原始性至关重要。
4 结束语
总的来说,这种针对面向对象动态多态性的测试方法关键步骤就是识别出动态多态性代码段,建立起动态多态性测试表,然后根据测试表的内容产生测试用例。这种方法在应对多影响因素、有复杂调用关系的动态多态性代码时尤为有效,它能帮助测试人员理清发生动态多态性调用时程序的实际执行路径和影响因素,方便测试人员以较少的测试用例来达到最大的动态多态性测试覆盖。本人使用该方法对多个有动态多态性的软件进行了类测试和类集成测试,均达到了比较满意的效果。
参考文献:
[1] 斯科瑞.面向对象设计原理与模式[M].北京:清华大学出版社,2009.
[2] Erich Gamma,Richard Helm,Ralph Johnson.设计模式:可复用面向对象软件的基础[M]. 北京:机械工业出版社,2007.
[3] 周建儒,余美璘.面向对象特征之多态性的分析[J].科技信息,2009(35).
[4] 和力,吴丽贤.关于C++虚函数底层实现机制的研究与分析[J].计算机工程與设计,2008(10).
[5] 郭滔.面向对象软件测试技术研究[J].科技信息,2011(3).
[6] 徐虹.面向对象的软件测试模型及策略研究[J].计算机与现代化,2005(3).
[7] 赵荣利,崔志明,陈建明.面向对象软件测试技术的研究与应用[J].计算机技术与发展,2007(1).