1 对认定的对象的测试:
OOA中认定的对象是对问题空间中的结构,其他系统,设备,被记忆的事件,系统涉及的人员等实际实例的抽象(参见[2])。对它的测试可以从如下方面考虑:
1.1 认定的对象是否全面,是否问题空间中所有涉及到的实例都反映在认定的抽象对象中。 1.2 认定的对象是否具有多个属性。只有一个属性的对象通常应看成其他对象的属性,而不是抽象为独立的对象。 1.3 对认定为同一对象的实例是否有共同的,区别于其他实例的共同属性。 1.4 对认定为同一对象的实例是否提供或需要相同的服务,如果服务随着不同的实例而变化,认定的对象就需要分解或利用继承性来分类表示。 1.5 如果系统没有必要始终保持对象代表的实例的信息,提供或者得到关于它的服务,认定的对象也无必要。 1.6 认定的对象的名称应该尽量准确,适用。
2 对认定的结构的测试
在Coad方法中,认定的结构指的是多种对象的组织方式,用来反映问题空间中的复杂实例和复杂关系。认定的结构分为两种:分类结构和组装结构。分类结构体现了问题空间中实例的一般与特殊的关系,组装结构体现了问题空间中实例整体与局部的关系。
2.1 对认定的分类结构的测试可从如下方面着手:
2.1.1 对于结构中的一种对象,尤其是处于高层的对象,是否在问题空间中含有不同于下一层对象的特殊可能性,即是否能派生出下一层对象。 2.1.2 对于结构中的一种对象,尤其是处于同一低层的对象,是否能抽象出在现实中有意义的更一般的上层对象。 2.1.3 对所有认定的对象,是否能在问题空间内向上层抽象出在现实中有意义的对象 2.1.4 高层的对象的特性是否完全体现下层的共性 2.1.5 低层的对象是否有高层特性基础上的特殊性
2.2 对认定的组装结构的测试从如下方面入手: 2.2.1 整体(对象)和部件(对象)的组装关系是否符合现实的关系。 2.2.2 整体(对象)的部件(对象)是否在考虑的问题空间中有实际应用。 2.2.3 整体(对象)中是否遗漏了反映在问题空间中有用的部件(对象)。 2.2.4 部件(对象)是否能够在问题空间中组装新的有现实意义的整体(对象)。
3 对认定的主题的测试
主题是在对象和结构的基础上更高一层的抽象,是为了提供OOA分析结果的可见性,如同文章对各部分内容的概要。对主题层的测试应该考虑以下方面:
3.1 贯彻George Miller 的"7+2"原则(参见[3]),如果主题个数超过7个,就要求对有较密切属性和服务的主题进行归并。
3.2 主题所反映的一组对象和结构是否具有相同和相近的属性和服务。
3.3 认定的主题是否是对象和结构更高层的抽象,是否便于理解OOA结果的概貌(尤其是对非技术人员的OOA 结果读者)。
3.4 主题间的消息联系(抽象)是否代表了主题所反映的对象和结构之间的所有关联。
4 对定义的属性和实例关联的测试
属性是用来描述对象或结构所反映的实例的特性。而实例关联是反映实例集合间的映射关系。对属性和实例关联的测试从如下方面考虑:
4.1 定义的属性是否对相应的对象和分类结构的每个现实实例都适用。 4.2 定义的属性在现实世界是否与这种实例关系密切。 4.3 定义的属性在问题空间是否与这种实例关系密切。 4.4 定义的属性是否能够不依赖于其他属性被独立理解。 4.5 定义的属性在分类结构中的位置是否恰当,低层对象的共有属性是否在上层对象属性体现。 4.6 在问题空间中每个对象的属性是否定义完整。 4.7 定义的实例关联是否符合现实。 4.8 在问题空间中实例关联是否定义完整,特别需要注意1-多和多-多的实例关联。
5 对定义的服务和消息关联的测试
定义的服务,就是定义的每一种对象和结构在问题空间所要求的行为。由于问题空中实例间必要的通信,在OOA 中相应需要定义消息关联(详细内容参见[3])。对定义的服务和消息关联的测试从如下方面进行:
5.1 对象和结构在问题空间的不同状态是否定义了相应的服务。 5.2 对象或结构所需要的服务是否都定义了相应的消息关联。 5.3 定义的消息关联所指引的服务提供是否正确。 5.4 沿着消息关联执行的线程是否合理,是否符合现实过程。 5.5 定义的服务是否重复,是否定义了能够得到的服务。
三、面向对象设计的测试(OOD Test)
通常的结构化的设计方法,用的"是面向作业的设计方法,它把系统分解以后,提出一组作业,这些作业是以过程实现系统的基础构造,把问题域的分析转化为求解域的设计,分析的结果是设计阶段的输入"(参见[1])。
而面向对象设计(OOD)采用"造型的观点",以OOA为基础归纳出类,并建立类结构或进一步构造成类库,实现分析结果对问题空间的抽象。OOD 归纳的类,可以是对象简单的延续,可以是不同对象的相同或相似的服务。由此可见,OOD不是在OOA上的另一思维方式的大动干戈,而是OOA的进一步细化和更高层的抽象。所以,OOD与OOA 的界限通常是难以严格区分的。OOD确定类和类结构不仅是满足当前需求分析的要求,更重要的是通过重新组合或加以适当的补充,能方便实现功能的重用和扩增,以不断适应用户的要求。因此,对OOD的测试,本文建议针对功能的实现和重用以及对OOA结果的拓展,从如下三方面考虑: ☆ 对认定的类的测试 ☆ 对构造的类层次结构的测试 ☆ 对类库的支持的测试
1 对认定的类的测试
OOD认定的类可以是OOA中认定的对象,也可以是对象所需要的服务的抽象,对象所具有的属性的抽象。认定的类原则上应该尽量基础性,这样才便于维护和重用。参考[4][5]中所提出的一些准则,测试认定的类:
1.1 是否含盖了OOA中所有认定的对象。 1.2 是否能体现OOA中定义的属性。 1.3 是否能实现OOA中定义的服务。 1.4 是否对应着一个含义明确的数据抽象。 1.5 是否尽可能少的依赖其他类。 1.6 类中的方法(C++:类的成员函数)是否单用途。
2 对构造的类层次结构的测试
为能充分发挥面向对象的继承共享特性,OOD的类层次结构,通常基于OOA中产生的分类结构的原则来组织,着重体现父类和子类间一般性和特殊性。两者概念上的差异,请参见[1]。在当前的问题空间,对类层次结构的主要要求是能在解空间构造实现全部功能的结构框架。为此,测试如下方面:
2.1 类层次结构是否含盖了所有定义的类。 2.2 是否能体现OOA中所定义的实例关联。 2.3 是否能实现OOA中所定义的消息关联。 2.4 子类是否具有父类没有的新特性。 2.5 子类间的共同特性是否完全在父类中得以体现。
3 对类库支持的测试
对类库的支持虽然也属于类层次结构的组织问题,但其强调的重点是再次软件开发的重用。由于它并不直接影响当前软件的开发和功能实现,因此,将其单独提出来测试,也可作为对高质量类层次结构的评估。参照[9]中提出的准则,拟订测试点如下:
3.1 一组子类中关于某种含义相同或基本相同的操作,是否有相同的接口(包括名字和参数表)。 3.2 类中方法(C++:类的成员函数)功能是否较单纯,相应的代码行是否较少([5]中建议为不超过30行)。 3.3 类的层次结构是否是深度大,宽度小。
四、面向对象编程的测试(OOP Test)
典型的面向对象程序具有继承、封装和多态的新特性,这使得传统的测试策略必须有所改变。封装是对数据的隐藏,外界只能通过被提供的操作来访问或修改数据,这样降低了数据被任意修改和读写的可能性,降低了传统程序中对数据非法操作的测试。继承是面向对象程序的重要特点,继承使得代码的重用率提高,同时也使错误传播的概率提高。继承使得传统测试遇见了这样一个难题:对继承的代码究竟应该怎样测试?(参见面向对象单元测试)。多态使得面向对象程序对外呈现出强大的处理能力,但同时却使得程序内"同一"函数的行为复杂化,测试时不得不考虑不同类型具体执行的代码和产生的行为。
面向对象程序是把功能的实现分布在类中。能正确实现功能的类,通过消息传递来协同实现设计要求的功能。正是这种面向对象程序风格,将出现的错误能精确的确定在某一具体的类。因此,在面向对象编程(OOP)阶段,忽略类功能实现的细则,将测试的目光集中在类功能的实现和相应的面向对象程序风格,主要体现为以下两个方面(假设编程使用C++语言)。
☆ 数据成员是否满足数据封装的要求 ☆ 类是否实现了要求的功能
1 数据成员是否满足数据封装的要求
数据封装是数据和数据有关的操作的集合。检查数据成员是否满足数据封装的要求,基本原则是数据成员是否被外界(数据成员所属的类或子类以外的调用)直接调用。更直观的说,当改编数据成员的结构时,是否影响了类的对外接口,是否会导致相应外界必须改动。值得注意,有时强制的类型转换会破坏数据的封装特性。例如: class Hiden {private: int a=1; char *p= "hiden";} class Visible {public: int b=2; char *s= "visible";} ….. ….. Hiden pp; Visible *qq=(Visible *)&pp; 在上面的程序段中,pp的数据成员可以通过qq被随意访问。
2 类是否实现了要求的功能
类所实现的功能,都是通过类的成员函数执行。在测试类的功能实现时,应该首先保证类成员函数的正确性。单独的看待类的成员函数,与面向过程程序中的函数或过程没有本质的区别,几乎所有传统的单元测试中所使用的方法,都可在面向对象的单元测试中使用。具体的测试方法在面向对象的单元测试中介绍。类函数成员的正确行为只是类能够实现要求的功能的基础,类成员函数间的作用和类之间的服务调用是单元测试无法确定的。因此,需要进行面向对象的集成测试。具体的测试方法在面向对象的集成测试中介绍。需要着重声明,测试类的功能,不能仅满足于代码能无错运行或被测试类能提供的功能无错,应该以所做的OOD结果为依据,检测类提供的功能是否满足设计的要求,是否有缺陷。必要时(如通过OOD结仍不清楚明确的地方)还应该参照OOA的结果,以之为最终标准。
五、面向对象的单元测试(OO Unit Test)
传统的单元测试是针对程序的函数、过程或完成某一定功能的程序块。沿用单元测试的概念,实际测试类成员函数。一些传统的测试方法在面向对象的单元测试中都可以使用。如等价类划分法,因果图法,边值分析法,逻辑覆盖法,路径分析法,程序插装法等等,方法的具体实现参见[6]。单元测试一般建议由程序员完成。
用于单元级测试进行的测试分析(提出相应的测试要求)和测试用例(选择适当的输入,达到测试要求),规模和难度等均远小于后面将介绍的对整个系统的测试分析和测试用例,而且强调对语句应该有100%的执行代码覆盖率。在设计测试用例选择输入数据时,可以基于以下两个假设:
1. 如果函数(程序)对某一类输入中的一个数据正确执行,对同类中的其他输入也能正确执行。该假设的思想可参见[6]中介绍的等价类划分。
2. 如果函数(程序)对某一复杂度的输入正确执行,对更高复杂度的输入也能正确执行。例如需要选择字符串作为输入时,基于本假设,就无须计较于字符串的长度。除非字符串的长度是要求固定的,如IP地址字符串。 在面向对象程序中,类成员函数通常都很小,功能单一,函数的间调用频繁,容易出现一些不宜发现的错误。例如:
· if (-1==write (fid, buffer, amount)) error_out(); 该语句没有全面检查write()的返回值,无意中断然假设了只有数据被完全写入和没有写入两种情况。当测试也忽略了数据部分写入的情况,就给程序遗留了隐患。
· 按程序的设计,使用函数strrchr()查找最后的匹配字符,但误程序中写成了函数strchr(),使程序功能实现时查找的是第一个匹配字符。
· 程序中将if (strncmp(str1,str2,strlen(str1)))误写成了 if (strncmp(str1,str2,strlen(str2)))。如果测试用例中使用的数据str1和str2长度一样,就无法检测出。
因此,在做测试分析和设计测试用例时,应该注意面向对象程序的这个特点,仔细的进行测试分析和设计测试用例,尤其是针对以函数返回值作为条件判断选择,字符串操作等情况。
面向对象编程的特性使得对成员函数的测试,又不完全等同于传统的函数或过程测试。尤其是继承特性和多态特性,使子类继承或过载的父类成员函数出现了传统测试中未遇见的问题。在[7]中,Brian Marick 给出了二方面的考虑:
1. 继承的成员函数是否都不需要测试?
根据[7]中的论述,对父类中已经测试过的成员函数,两种情况需要在子类中重新测试:a)继承的成员函数在子类中做了改动;b)成员函数调用了改动过的成员函数的部分。例如:
假设父类Bass有两个成员函数:Inherited()和Redefined(),子类Derived只对Redefined()做了改动。 Derived::Redefined()显然需要重新测试。对于Derived::Inherited(),如果它有调用Redefined()的语句(如:x=x/Redefined()),就需要重新测试,反之,无此必要。
2. 对父类的测试是否能照搬到子类?
援用上面的假设,Base::Redefined()和Derived::Redefined()已经是不同的成员函数,它们有不同的服务说明和执行。对此,照理应该对 Derived::Redefined()重新测试分析,设计测试用例。但由于面向对象的继承使得两个函数有相似,故只需在 Base::Redefined()的测试要求和测试用例上添加对Derived::Redfined()新的测试要求和增补相应的测试用例。例如:
Base::Redefined()含有如下语句 If (value<0) message ("less"); else if (value==0) message ("equal"); else message ("more"); Derived::Redfined()中定义为 If (value<0) message ("less"); else if (value==0) message ("It is equal"); else {message ("more"); if (value==88)message("luck");}
在原有的测试上,对Derived::Redfined()的测试只需做如下改动:将value==0的测试结果期望改动;增加value==88的测试。
多态有几种不同的形式,如参数多态,包含多态,过载多态。包含多态和过载多态在面向对象语言中通常体现在子类与父类的继承关系,对这两种多态的测试参见上述对父类成员函数继承和过载的论述。包含多态虽然使成员函数的参数可有多种类型,但通常只是增加了测试的繁杂。对具有包含多态的成员函数测试时,只需要在原有的测试分析和基础上扩大测试用例中输入数据的类型的考虑。对类为粒度进行面向对象的单元测试,可参考[10]中关于如何从MtSS生成测试用例的说明。
六、面向对象的集成测试(OO Integrate Test)
传统的集成测试,是由底向上通过集成完成的功能模块进行测试,一般可以在部分程序编译完成的情况下进行。而对于面向对象程序,相互调用的功能是散布在程序的不同类中,类通过消息相互作用申请和提供服务。类的行为与它的状态密切相关,状态不仅仅是体现在类数据成员的值,也许还包括其他类中的状态信息。由此可见,类相互依赖极其紧密,根本无法在编译不完全的程序上对类进行测试。所以,面向对象的集成测试通常需要在整个程序编译完成后进行。此外,面向对象程序具有动态特性,程序的控制流往往无法确定,因此也只能对整个编译后的程序做基于黑盒子的集成测试。
面向对象的集成测试能够检测出相对独立的单元测试无法检测出的那些类相互作用时才会产生的错误。基于单元测试对成员函数行为正确性的保证,集成测试只关注于系统的结构和内部的相互作用。面向对象的集成测试可以分成两步进行:先进行静态测试,再进行动态测试。
静态测试主要针对程序的结构进行,检测程序结构是否符合设计要求。现在流行的一些测试软件都能提供一种称为"可逆性工程"的功能,即通过原程序得到类关系图和函数功能调用关系图,例如International Software Automation 公司的Panorama-2 for Windows95、Rational公司的Rose C++ Analyzer等,将"可逆性工程"得到的结果与OOD的结果相比较,检测程序结构和实现上是否有缺陷。换句话说,通过这种方法检测OOP是否达到了设计要求。
动态测试设计测试用例时,通常需要上述的功能调用结构图、类关系图或者实体关系图为参考,确定不需要被重复测试的部分,从而优化测试用例,减少测试工作量,使得进行的测试能够达到一定覆盖标准。测试所要达到的覆盖标准可以是:达到类所有的服务要求或服务提供的一定覆盖率;依据类间传递的消息,达到对所有执行线程的一定覆盖率;达到类的所有状态的一定覆盖率等。同时也可以考虑使用现有的一些测试工具来得到程序代码执行的覆盖率。
具体设计测试用例,可参考下列步骤: 1. 先选定检测的类,参考OOD分析结果,仔细出类的状态和相应的行为,类或成员函数间传递的消息,输入或输出的界定等。 2. 确定覆盖标准。 3. 利用结构关系图确定待测类的所有关联。 4. 根据程序中类的对象构造测试用例,确认使用什么输入激发类的状态、使用类的服务和期望产生什么行为等。
值得注意,设计测试用例时,不但要设计确认类功能满足的输入,还应该有意识的设计一些被禁止的例子,确认类是否有不合法的行为产生,如发送与类状态不相适应的消息,要求不相适应的服务等。根据具体情况,动态的集成测试,有时也可以通过系统测试完成。
七、面向对象的系统测试(OO System Test)
通过单元测试和集成测试,仅能保证软件开发的功能得以实现。但不能确认在实际运行时,它是否满足用户的需要,是否大量存在实际使用条件下会被诱发产生错误的隐患。为此,对完成开发的软件必须经过规范的系统测试。换个角度说,开发完成的软件仅仅是实际投入使用系统的一个组成部分,需要测试它与系统其他部分配套运行的表现,以保证在系统各部分协调工作的环境下也能正常工作。在后面对ZXM10收发台系统测试的叙述可以看到,其他的系统设备(如监控台,图象台,E1接入设备,摄像头等)如何配合收发台的系统测试。
系统测试应该尽量搭建与用户实际使用环境相同的测试平台,应该保证被测系统的完整性,对临时没有的系统设备部件,也应有相应的模拟手段。系统测试时,应该参考OOA分析的结果,对应描述的对象、属性和各种服务,检测软件是否能够完全"再现"问题空间。系统测试不仅是检测软件的整体行为表现,从另一个侧面看,也是对软件开发设计的再确认。
这里说的系统测试是对测试步骤的抽象描述。它体现的具体测试内容包括:
· 功能测试:测试是否满足开发要求,是否能够提供设计所描述的功能,是否用户的需求都得到满足。功能测试是系统测试最常用和必须的测试,通常还会以正式的软件说明书为测试标准。
· 强度测试:测试系统的能力最高实际限度,即软件在一些超负荷的情况,功能实现情况。如要求软件某一行为的大量重复、输入大量的数据或大数值数据、对数据库大量复杂的查询等。
· 性能测试:测试软件的运行性能。这种测试常常与强度测试结合进行,需要事先对被测软件提出性能指标,如传输连接的最长时限、传输的错误率、计算的精度、记录的精度、响应的时限和恢复时限等。
· 安全测试:验证安装在系统内的保护机构确实能够对系统进行保护,使之不受各种非常的干扰。安全测试时需要设计一些测试用例试图突破系统的安全保密措施,检验系统是否有安全保密的漏洞。
· 恢复测试:采用人工的干扰使软件出错,中断使用,检测系统的恢复能力,特别是通讯系统。恢复测试时,应该参考性能测试的相关测试指标。
· 可用性测试:测试用户是否能够满意使用。具体体现为操作是否方便,用户界面是否友好等。
· 安装/卸载测试(install/uninstall test)等等。
系统测试需要对被测的软件结合需求分析做仔细的测试分析,建立测试用例。
|