面向对象软件的测试
1q(t9K$^`-s0T/d0 用户使用低质量的软件,在运行过程中会产生各种各样的问题,可能带来不同程度的严重后果,轻者影响系统的正常工作,重者造成事故,损失生命财产。软件测试是保证软件质量的最重要的手段。什么是软件测试?1983年IEEE定义为:使用人工或自动手段来运行或测定某个系统的过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别。软件测试网:yl"o(^[Q"ykU (Hz[!MU0 现代的软件开发工程是将整个软件开发过程明确的划分为几个阶段(参见[6]的论述),将复杂问题具体按阶段加以解决。这样,在软件的整个开发过程中,可以对每一阶段提出若干明确的监控点,作为各阶段目标实现的检验标准,从而提高开发过程的可见度和保证开发过程的正确性。经验证明,软件的质量不仅是体现在程序的正确性上,它和编码以前所做的需求分析,软件设计密切相关。软件使用中出现的错误,不一定是编程人员在编码阶段引入的,很可能在程序设计,甚而需求分析时就埋下了祸因。这时,对错误的纠正往往不能通过可能会诱发更多错误的简单的修修补补,而必须追溯到软件开发的最初阶段。这无疑增大了软件的开发费用。因此,为了保证软件的质量,我们应该着眼于整个软件生存期,特别是着眼于编码以前的各开发阶段的工作。这样,软件测试的概念和实施范围必须扩充,应该包括在整个开发各阶段的复查、评估和检测。由此,广义的软件测试实际是由确认、验证、测试三个方面组成(注:本文对后面用广义测试概念处不另加标识):软件测试网1oKj `Tj0m,N 确认:是评估将要开发的软件产品是否是正确无误、可行和有价值的。比如,将要开发的软件是否会满足用户提出的要求,是否能在将来的实际使用环境中正确稳定的运行,是否存在隐患等。这里包含了对用户需求满足程度的评价。确认意味着确保一个待开发软件是正确无误的,是对软件开发构想的检测。软件测试网(b,]"D
WZ,Dx~1U3r 验证:是检测软件开发的每个阶段、每个步骤的结果是否正确无误,是否与软件开发各阶段的要求或期望的结果相一致。验证意味着确保软件是会正确无误的实现软件的需求,开发过程是沿着正确的方向在进行。软件测试网8S
e*B!Jr6q[Wj`.U 测试:与狭隘的测试概念统一。通常是经过单元测试、集成测试、系统测试三个环节。 在整个软件生存期(参见[6]中的划分),确认、验证、测试分别有其侧重的阶段。确认主要体现在计划阶段、需求分析阶段、也会出现在测试阶段;验证主要体现在设计阶段和编码阶段;测试主要体现在编码阶段和测试阶段。事实上,确认、验证、测试是相辅相成的。确认无疑会产生验证和测试的标准,而验证和测试通常又会帮助完成一些确认,特别是在系统测试阶段。软件测试网#? tb]\q6z \ 面向对象技术是一种全新的软件开发技术,正逐渐代替被广泛使用的面向过程开发方法,被看成是解决软件危机的新兴技术。面向对象技术产生更好的系统结构,更规范的编程风格,极大的优化了数据使用的安全性,提高了程序代码的重用,一些人就此认为面向对象技术开发出的程序无需进行测试。应该看到,尽管面向对象技术的基本思想保证了软件应该有更高的质量,但实际情况却并非如此,因为无论采用什么样的编程技术,编程人员的错误都是不可避免的,而且由于面向对象技术开发的软件代码重用率高,更需要严格测试,避免错误的繁衍。因此,软件测试并没有面向对象编程的兴起而丧失掉它的重要性。 从1982年在美国北卡罗来纳大学召开首次软件测试的正式技术会议至今,软件测试理论迅速发展,并相应出现了各种软件测试方法,使软件测试技术得到极大的提高。然而,一度实践证明行之有效的软件测试对面向对象技术开发的软件多少显得有些力不从心。尤其是面向对象技术所独有的多态,继承,封装等新特点,产生了传统语言设计所不存在的错误可能性,或者使得传统软件测试中的重点不再显得突出,或者使原来测试经验认为和实践证明的次要方面成为了主要问题。例如: k$dX&J"QX q0N0 在传统的面向过程程序中,对于函数软件测试网0RLs'`6wN/aT
t} N!g9`9]5q&\-E0 面向对象程序的结构不再是传统的功能模块结构,作为一个整体,原有集成测试所要求的逐步将开发的模块搭建在一起进行测试的方法已成为不可能。而且,面向对象软件抛弃了传统的开发模式,对每个开发阶段都有不同以往的要求和结果,已经不可能用功能细化的观点来检测面向对象分析和设计的结果。因此,传统的测试模型对面向对象软件已经不再适用。针对面向对象软件的开发特点,应该有一种新的测试模型。 ]r4L ^]0]e4dz0W{/T8V}:x2}k0 2HTs:S k k"V3zs?0,x7?u*|zBwM0一、面向对象测试模型(Object-Orient Test Model)软件测试网9hc0@xXn 软件测试网){H _8Kk&g&Bl R}面向对象的开发模型突破了传统的瀑布模型,将开发分为面向对象分析(OOA),面向对象设计(OOD),和面向对象编程(OOP)三个阶段。分析阶段产生整个问题空间的抽象描述,在此基础上,进一步归纳出适用于面向对象编程语言的类和类结构,最后形成代码。由于面向对象的特点,采用这种开发模型能有效的将分析设计的文本或图表代码化,不断适应用户需求的变动。针对这种开发模型,结合传统的测试步骤的划分,本文建议一种整个软件开发过程中不断测试的测试模型,使开发阶段的测试与编码完成后的单元测试、集成测试、系统测试成为一个整体。测试模型如下图所示: urj4yns} x0 |
1nUN)Wh]0d0 |
0 面向对象编程
软件测试网
M/tlV!v3Nq'w/Q}'Wi3JE OOA Test:面向对象分析的测试 OOD Test:面向对象设计的测试
?q#Q a3u)k Aca0 OOA Test和OOD Test 是对分析结果和设计结果的测试,主要是对分析设计产生的文本进行,是软件开发前期的关键性测试。OOP Test主要针对编程风格和程序代码实现进行测试,其主要的测试内容在面向对象单元测试和面向对象集成测试中体现。面向对象单元测试是对程序内部具体单一的功能模块的测试,如果程序是用C++语言实现,主要就是对类成员函数的测试。面向对象单元测试是进行面向对象集成测试的基础。面向对象集成测试主要对系统内部的相互服务进行测试,如成员函数间的相互作用,类间的消息传递等。面向对象集成测试不但要基于面向对象单元测试,更要参见OOD或OOD Test结果(详见后叙述)。面向对象系统测试是基于面向对象集成测试的最后阶段的测试,主要以用户需求为测试标准,需要借鉴OOA或OOA Test结果。软件测试网;iM k;I!G8C#x4~ 尽管上述各阶段的测试构成一相互作用的整体,但其测试的主体、方向和方法各有不同,且为叙述的方便,本文接下来将从OOA,OOD,OOP,单元测试,集成测试,系统测试六个方面分别介绍对面向对象软件的测试。软件测试网
mY6J/z+B]n5r3` 对由OOA,OOP,OOD这三个开发阶段所构成的开发模型、各阶段应该完成的目标以及结果报告的形式等详细介绍,已经超出本文内容,请参见[1]。 Z{glvG7k!e5hQ0x$z+SL5p0二、面向对象分析的测试(OOA Test)软件测试网)n(~7OwW$S"G7Q(d0wN 软件测试网1@UE5p|4o7g 传统的面向过程分析是一个功能分解的过程,是把一个系统看成可以分解的功能的集合。这种传统的功能分解分析法的着眼点在于一个系统需要什么样的信息处理方法和过程,以过程的抽象来对待系统的需要。而面向对象分析(OOA)是"把E-R图和语义网络模型,即信息造型中的概念,与面向对象程序设计语言中的重要概念结合在一起而形成的分析方法"(参见[1]),最后通常是得到问题空间的图表的形式描述。软件测试网\nWR3uu OOA直接映射问题空间,全面的将问题空间中实现功能的现实抽象化。将问题空间中的实例抽象为对象(不同于C++中的对象概念),用对象的结构反映问题空间的复杂实例和复杂关系,用属性和服务表示实例的特性和行为。对一个系统而言,与传统分析方法产生的结果相反,行为是相对稳定的,结构是相对不稳定的,这更充分反映了现实的特性。OOA的结果是为后面阶段类的选定和实现,类层次结构的组织和实现提供平台。因此,OOA对问题空间分析抽象的不完整,最终会影响软件的功能实现,导致软件开发后期大量可避免的修补工作;而一些冗余的对象或结构会影响类的选定、程序的整体结构或增加程序员不必要的工作量。因此,本文对OOA的测试重点在其完整性和冗余性。软件测试网B;x?"j:L5JP 7UfPq~M~f
^1S0 尽管OOA的测试是一个不可分割的系统过程,为叙述的方便,鉴于[2]中Coad方法所提出的OOA实现步骤,对OOA阶段的测试划分为以下五个方面: |
F9P t0q;_sy0 |
软件测试网#M7wXX*v&L:bK
Hy
P 1 对认定的对象的测试:软件测试网2k C$z4M#g3B#Q OOA中认定的对象是对问题空间中的结构,其他系统,设备,被记忆的事件,系统涉及的人员等实际实例的抽象(参见[2])。对它的测试可以从如下方面考虑:软件测试网8S%\u*ie)O1}pAC 1.1 认定的对象是否全面,是否问题空间中所有涉及到的实例都反映在认定的抽象对象中。软件测试网.O [%[+E{6V mL k 2 对认定的结构的测试软件测试网KjmZKyB
` 在Coad方法中,认定的结构指的是多种对象的组织方式,用来反映问题空间中的复杂实例和复杂关系。认定的结构分为两种:分类结构和组装结构。分类结构体现了问题空间中实例的一般与特殊的关系,组装结构体现了问题空间中实例整体与局部的关系。软件测试网/V6H} _8Jp"_:rp^/F1}EB 2.1 对认定的分类结构的测试可从如下方面着手: "]9ZDV|_#f0 2.1.1 对于结构中的一种对象,尤其是处于高层的对象,是否在问题空间中含有不同于下一层对象的特殊可能性,即是否能派生出下一层对象。 ,s m%['Kh\8d.O T7K8m02.2 对认定的组装结构的测试从如下方面入手:软件测试网)s8O]%@0F2R
N:h -Q-FY(]1j'k9l O03 对认定的主题的测试软件测试网;]%@y9dLE7h(X Z
d w
BY9Yk{0 主题是在对象和结构的基础上更高一层的抽象,是为了提供OOA分析结果的可见性,如同文章对各部分内容的概要。对主题层的测试应该考虑以下方面:软件测试网o\*G3l)HqpC-I 3.1 贯彻George Miller 的"7+2"原则(参见[3]),如果主题个数超过7个,就要求对有较密切属性和服务的主题进行归并。软件测试网*}LpOM?"w.u,R 3.2 主题所反映的一组对象和结构是否具有相同和相近的属性和服务。 3.3 认定的主题是否是对象和结构更高层的抽象,是否便于理解OOA结果的概貌(尤其是对非技术人员的OOA 结果读者)。软件测试网BvL]4W,U %@ZsI!F J0 3.4 主题间的消息联系(抽象)是否代表了主题所反映的对象和结构之间的所有关联。 \(T o U"T&E%c0!b R Hm6^7_ w~04 对定义的属性和实例关联的测试 属性是用来描述对象或结构所反映的实例的特性。而实例关联是反映实例集合间的映射关系。对属性和实例关联的测试从如下方面考虑: 4.1 定义的属性是否对相应的对象和分类结构的每个现实实例都适用。软件测试网"Q2f6Z N~eq 5 对定义的服务和消息关联的测试软件测试网q0tW(O b$w:UD,k2C 定义的服务,就是定义的每一种对象和结构在问题空间所要求的行为。由于问题空中实例间必要的通信,在OOA 中相应需要定义消息关联(详细内容参见[3])。对定义的服务和消息关联的测试从如下方面进行:软件测试网XK%C1U| 6iZ k,Z\0wW0 5.1 对象和结构在问题空间的不同状态是否定义了相应的服务。软件测试网F,~ w~B?6p 三、面向对象设计的测试(OOD Test) 0n`2NS#[0y$Ug,G@+FC,chR0 通常的结构化的设计方法,用的"是面向作业的设计方法,它把系统分解以后,提出一组作业,这些作业是以过程实现系统的基础构造,把问题域的分析转化为求解域的设计,分析的结果是设计阶段的输入"(参见[1])。 %\-wP_G0 而面向对象设计(OOD)采用"造型的观点",以OOA为基础归纳出类,并建立类结构或进一步构造成类库,实现分析结果对问题空间的抽象。OOD 归纳的类,可以是对象简单的延续,可以是不同对象的相同或相似的服务。由此可见,OOD不是在OOA上的另一思维方式的大动干戈,而是OOA的进一步细化和更高层的抽象。所以,OOD与OOA 的界限通常是难以严格区分的。OOD确定类和类结构不仅是满足当前需求分析的要求,更重要的是通过重新组合或加以适当的补充,能方便实现功能的重用和扩增,以不断适应用户的要求。因此,对OOD的测试,本文建议针对功能的实现和重用以及对OOA结果的拓展,从如下三方面考虑:软件测试网:L H{:S,I+OE$E
sS-h g]H]{;Md01 对认定的类的测试 ;_YBvX7NA-kb0 OOD认定的类可以是OOA中认定的对象,也可以是对象所需要的服务的抽象,对象所具有的属性的抽象。认定的类原则上应该尽量基础性,这样才便于维护和重用。参考[4][5]中所提出的一些准则,测试认定的类:软件测试网L:B;GX3lsh;@:l 1.1 是否含盖了OOA中所有认定的对象。软件测试网I8N~2Cel}4lh
c 2 对构造的类层次结构的测试软件测试网"B+O"mh!ZAa 为能充分发挥面向对象的继承共享特性,OOD的类层次结构,通常基于OOA中产生的分类结构的原则来组织,着重体现父类和子类间一般性和特殊性。两者概念上的差异,请参见[1]。在当前的问题空间,对类层次结构的主要要求是能在解空间构造实现全部功能的结构框架。为此,测试如下方面: 2.1 类层次结构是否含盖了所有定义的类。软件测试网9N%_YrCG)SN.|4} m.BRi)in$Q P03 对类库支持的测试 ,xuje {7k+y$Rtt0 对类库的支持虽然也属于类层次结构的组织问题,但其强调的重点是再次软件开发的重用。由于它并不直接影响当前软件的开发和功能实现,因此,将其单独提出来测试,也可作为对高质量类层次结构的评估。参照[9]中提出的准则,拟订测试点如下: 3.1 一组子类中关于某种含义相同或基本相同的操作,是否有相同的接口(包括名字和参数表)。 四、面向对象编程的测试(OOP Test)软件测试网 _i.a"@6A J N"KO-@'bC0 典型的面向对象程序具有继承、封装和多态的新特性,这使得传统的测试策略必须有所改变。封装是对数据的隐藏,外界只能通过被提供的操作来访问或修改数据,这样降低了数据被任意修改和读写的可能性,降低了传统程序中对数据非法操作的测试。继承是面向对象程序的重要特点,继承使得代码的重用率提高,同时也使错误传播的概率提高。继承使得传统测试遇见了这样一个难题:对继承的代码究竟应该怎样测试?(参见面向对象单元测试)。多态使得面向对象程序对外呈现出强大的处理能力,但同时却使得程序内"同一"函数的行为复杂化,测试时不得不考虑不同类型具体执行的代码和产生的行为。软件测试网 hM_9G$Ik
E+[ 面向对象程序是把功能的实现分布在类中。能正确实现功能的类,通过消息传递来协同实现设计要求的功能。正是这种面向对象程序风格,将出现的错误能精确的确定在某一具体的类。因此,在面向对象编程(OOP)阶段,忽略类功能实现的细则,将测试的目光集中在类功能的实现和相应的面向对象程序风格,主要体现为以下两个方面(假设编程使用C++语言)。 ☆ 数据成员是否满足数据封装的要求软件测试网r]fW
XT9m 1 数据成员是否满足数据封装的要求 数据封装是数据和数据有关的操作的集合。检查数据成员是否满足数据封装的要求,基本原则是数据成员是否被外界(数据成员所属的类或子类以外的调用)直接调用。更直观的说,当改编数据成员的结构时,是否影响了类的对外接口,是否会导致相应外界必须改动。值得注意,有时强制的类型转换会破坏数据的封装特性。例如:软件测试网B.O'r+B&``5^'Mp 5K5pr;gR.n02 类是否实现了要求的功能 类所实现的功能,都是通过类的成员函数执行。在测试类的功能实现时,应该首先保证类成员函数的正确性。单独的看待类的成员函数,与面向过程程序中的函数或过程没有本质的区别,几乎所有传统的单元测试中所使用的方法,都可在面向对象的单元测试中使用。具体的测试方法在面向对象的单元测试中介绍。类函数成员的正确行为只是类能够实现要求的功能的基础,类成员函数间的作用和类之间的服务调用是单元测试无法确定的。因此,需要进行面向对象的集成测试。具体的测试方法在面向对象的集成测试中介绍。需要着重声明,测试类的功能,不能仅满足于代码能无错运行或被测试类能提供的功能无错,应该以所做的OOD结果为依据,检测类提供的功能是否满足设计的要求,是否有缺陷。必要时(如通过OOD结仍不清楚明确的地方)还应该参照OOA的结果,以之为最终标准。 }:p^JCu5|#^TYx0;tn)]"E"O_7GA0五、面向对象的单元测试(OO Unit Test) {d3AG#T0 软件测试网,z.q!x9aV(uv_ | 传统的单元测试是针对程序的函数、过程或完成某一定功能的程序块。沿用单元测试的概念,实际测试类成员函数。一些传统的测试方法在面向对象的单元测试中都可以使用。如等价类划分法,因果图法,边值分析法,逻辑覆盖法,路径分析法,程序插装法等等,方法的具体实现参见[6]。单元测试一般建议由程序员完成。软件测试网NQN
m c.oy#z 用于单元级测试进行的测试分析(提出相应的测试要求)和测试用例(选择适当的输入,达到测试要求),规模和难度等均远小于后面将介绍的对整个系统的测试分析和测试用例,而且强调对语句应该有100%的执行代码覆盖率。在设计测试用例选择输入数据时,可以基于以下两个假设:软件测试网yA?ib}
] 1. 如果函数(程序)对某一类输入中的一个数据正确执行,对同类中的其他输入也能正确执行。该假设的思想可参见[6]中介绍的等价类划分。 2. 如果函数(程序)对某一复杂度的输入正确执行,对更高复杂度的输入也能正确执行。例如需要选择字符串作为输入时,基于本假设,就无须计较于字符串的长度。除非字符串的长度是要求固定的,如IP地址字符串。 · if (-1==write (fid, buffer, amount)) error_out();软件测试网-H!{y+d
spEz"q · 按程序的设计,使用函数strrchr()查找最后的匹配字符,但误程序中写成了函数strchr(),使程序功能实现时查找的是第一个匹配字符。 · 程序中将if (strncmp(str1,str2,strlen(str1)))误写成了软件测试网F.RMP^ 因此,在做测试分析和设计测试用例时,应该注意面向对象程序的这个特点,仔细的进行测试分析和设计测试用例,尤其是针对以函数返回值作为条件判断选择,字符串操作等情况。软件测试网V-LS
D
N R4X 2XP{6?0KD F0 面向对象编程的特性使得对成员函数的测试,又不完全等同于传统的函数或过程测试。尤其是继承特性和多态特性,使子类继承或过载的父类成员函数出现了传统测试中未遇见的问题。在[7]中,Brian Marick 给出了二方面的考虑:
Rwda"U$eG%KK01. 继承的成员函数是否都不需要测试?软件测试网#A$N"g FF(qm{u Ndw(k|{5y0 根据[7]中的论述,对父类中已经测试过的成员函数,两种情况需要在子类中重新测试:a)继承的成员函数在子类中做了改动;b)成员函数调用了改动过的成员函数的部分。例如:软件测试网}-jYqb 假设父类Bass有两个成员函数:Inherited()和Redefined(),子类Derived只对Redefined()做了改动。 Derived::Redefined()显然需要重新测试。对于Derived::Inherited(),如果它有调用Redefined()的语句(如:x=x/Redefined()),就需要重新测试,反之,无此必要。软件测试网8Wm1w'E4e'~gl8u e7K?%Q)F'y-q(MV02. 对父类的测试是否能照搬到子类?软件测试网u)a6EK(kG#f(n 援用上面的假设,Base::Redefined()和Derived::Redefined()已经是不同的成员函数,它们有不同的服务说明和执行。对此,照理应该对 Derived::Redefined()重新测试分析,设计测试用例。但由于面向对象的继承使得两个函数有相似,故只需在 Base::Redefined()的测试要求和测试用例上添加对Derived::Redfined()新的测试要求和增补相应的测试用例。例如:软件测试网Z"T5nPB+Tn*a nxD)M&`d0 Base::Redefined()含有如下语句 |