软件测试
1 基本概念
1.1 软件测试
为了保证软件的质量和可靠性,人们力求在分析、设计等各个开发阶段结束之前,对软件进行严格的技术评审。
软件测试是为了发现错误而执行程序的过程。
或者说
软件测试是根据软件开发各阶段的规格说明和程序的内部结构而精心设计一批测试用例(即输入数据及其预期的输出结果),并利用这些测试用例去运行程序,以发现程序错误的过程。
1.2 目的
目的:以最少的时间和人力,系统的找出软件中潜在的各种错误和缺陷。
Grenford J. Myers 就软件测试目的提出以下观点:
- 测试是程序的执行过程,目的在于发现错误;
- 一个好的测试用例在于能发现至今未发现的错误;
- 一个成功的测试是发现了至今未发现的错误的测试;
测试不能表明软件中不存在的错误,它只能说明软件中存在的错误。
1.3 原则
- 应当把“尽早地和不断地进行软件测试”作为软件开发者的座右铭。
- 测试用例应由测试输入数据和与之对应的预期输出结果这两部分组成。
- 程序员应避免检查自己的程序。
- 在设计测试用例时,应当包括合理的输入条件和不合理的输入条件。
- 充分注意测试中的集群现象。
- 严格执行测试计划,排除测试的随意性。
- 应当对每一个测试结果作全面检查。
- 妥善保存测试计划、测试用例、出错统计和最终分析报告,为未来实施的维护提供方便。
1.4 周期
1.5 测试与开发过程的关系
1.6 白盒测试与黑盒测试
任何工程产品都可以使用以下两种方法之一进行测试:
- 已知产品的功能设计规格,可以通过测试证明每个实现了的功能是否符合要求。
- 已知产品内部的工作过程,可以通过测试证明每种内部操作是否符合设计规格要求,所有内部成分是否已经过检查。
前者是黑盒测试,后者是白盒测试。
黑盒测试方法主要是为了发现:是否有不正确或遗漏了的功能?输入能否正确地接受?能否输出正确的结果?是否有数据结构错误或外部信息(例如数据文件)访问错误?性能上是否能够满足需求?是否有初始化或终止性错误?
所以,用黑盒测试发现程序中的错误,必须在所有可能的满足输入条件和输出条件中确定测试数据,检查程序是否都能产生正确的输出。
白盒测试主要是对程序模块进行检查:对程序模块的所有独立的执行路径至少测试一次;对所有的逻辑判定,取“真”与取“假”的两种情况都至少测试一次;在循环的边界和运行界限内执行循环体;测试内部数据结构的有效性等等。
因此白盒测试又称为结构测试或逻辑驱动测试。
1.7 测试用例
在测试阶段,穷举测试不可行,必须精心设计测试用例,从数量极大的可用测试用例中精心地挑选少量的测试数据,使得采用这些测试数据能够达到最佳的测试效果,或者说它们能够高效率地把隐藏的错误尽可能多的揭露出来。
事实说明,软件测试有一个致命的缺陷,即测试的不完全、不彻底性。由于任何程序只能进行少量(相对于穷举的巨大数量而言)的有限的测试,所以在发现错误时能说明程序有问题,但在未发现错误时,不能说明程序中没有错误、没有问题。
2 白盒测试的测试用例设计
2.1 逻辑覆盖
逻辑覆盖是以程序内部的逻辑结构为基础的设计测试用例的技术,它属于白盒测试。
逻辑覆盖可分为:语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖、路径覆盖。
以下将分别做出扼要的介绍。在所介绍的几种逻辑覆盖中,均以下图所示的程序为例。其中有两个判断,每个判断都包含符合条件的逻辑表达式,并且,符号“∧”表示“AND”运算,“∨”表示“OR”运算。a、b、c、d、e 分别为不同的路径。
若把各条路径应满足的逻辑表达式综合起来,可以进行如下推导,其中使用叹号“! ”表示“非”运算。
L1(a->c->e)
= { (A>1) and (B=0) } and { (A=2) or (X/A>1) }
= (A>1) and (B=0) and (A=2) or (A>1) and (B=0) and (X/A>1)
= (A=2) and (B=0) or (A>1) and (B=0) and (X/A>1)
L2(a->b->d)
= !{ (A>1) and (B=0) } and !{ (A=2) or (X>1) }
= { !(A>1) or !(B=0) } and { !(A=2) and !(X>1) }
= !(A>1 ) and !(A=2 ) and !(X>1 ) or !(B=0) and !(A=2) and !(X>1)
= (A≦1) and (X≦1) or (B≠0) and (A≠2) and (X≦1)
L3(a->b->e)
= !{ (A>1) and (B=0)} and { (A=2) or (X>1) }
= { !(A>1) or !(B=0) } and { (A=2) or (X>1) }
= !(A>1) and (X>1) or !(B=0) and (A=2) or !(B=0) and (X>1)
= (A≦1) and (X>1) or (B≠0) and (A=2) or (B≠0) and (X>1)
L4(a->c->d)
= { (A>1) and (B=0) } and !{ (A=2) or (X/A>1) }
= (A>1) and (B=0) and (A≠2) and (X/A≦1)
2.1.1 语句覆盖
语句覆盖就是设计若干个测试用例,运行被测程序,使得每一个可执行语句至少执行一次。
如图例中的的程序,正好所有的可执行语句都在路径L1上,所以选择路径L1设计测试用例,就可以覆盖所有的可执行语句。
测试用例的设计格式如下:
【输入的(A,B,X),输出的(A,B,X)】
为图例设计的满足语句覆盖的测试用例是:
【(2,0,4),(2,0,3)】覆盖 ace【l1】
从程序中每个可执行语句都得到执行这一点来看,语句覆盖的方法似乎能够比较全面地检验每一个可执行语句。但与其它覆盖相比,语句覆盖是最弱的逻辑覆盖准则。
2.1.2 判定覆盖
判定覆盖就是设计若干个测试用例,运行被测试程序,使得程序中每个判断的取真分支和取假分支至少经历一次。判定覆盖又称为分支覆盖。
例如图例中的例子,如果选择路径L1和L2,可得满足要求的测试用例:
【(2,0,4),(2,0,3)】覆盖 ace【L1】
【(1,1,1),(1,1,1)】覆盖 abd【L2】
如果选择路径L3和L4,还可得另一组可用的测试用例:
【(2,1,1),(2,1,2)】覆盖 abe【L3】
【(3,0,3),(3,1,1)】覆盖 acd【L4】
所以,测试用例的取法不唯一。注意有例外情形,例如,若把图例中例子的第二个判断条件 X>1 错写成 X<1,那么利用上面两组测试用例,仍能得到同样结果。这表明,只是判定覆盖,还不能保证一定能查出在判断条件中存在的错误。因此,还需要更强的逻辑覆盖准则校验判断内部条件。
以上仅讨论了两出口的判断,我们还应该把判定覆盖准则扩充到多出口判断(如case语句)的情况。
2.1.3 条件覆盖
条件覆盖就是设计若干个测试用例,运行被测程序,使得程序中每个判断的每个条件的可能取值至少执行一次。
如图例中,我们可以事先对所有条件的取值加以标记。例如:
对于第一个判断:
- 条件A>1取真值为T1,取假值为!T1;
- 条件B=0取真值为T2,取假值为!T2;
对于第二个判断: - 条件A=2取真值为T3,取假值为!T3;
- 条件X>1取真值为T4,取假值为!T4;
则可选取测试用例如下:
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
【(2,0,4),(2,0,3)】 | ace(L1) | T1,T2,T3,T4 | c,e |
【(1,0,1),(1,0,1)】 | abd(L2) | !T1,T2,!T3,T4 | b,d |
【(2,1,1),(2,1,2)】 | abe(L3) | T1,!T2,T3,!T4 | b,e |
或
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
【(1,0,3),(1,0,4)】 | abe(L3) | !T1,T2,!T3,T4 | b,e |
【(2,1,1),(2,1,2)】 | abe(L3) | T1,!T2,T3,!T4 | b,e |
注意,前一组测试用例不但覆盖了所有判断的取真分支和取假分支,而且覆盖了判断中所有条件的可能取值。但是最后一组测试用例虽满足了条件覆盖,却只覆盖了第一个判断的取假分支和第二个判断的取真分支,不满足判定覆盖的要求。
2.1.4 判定-条件覆盖
判定-条件覆盖就是设计足够的测试用例,使得判断中每个条件的所有可能取值至少执行一次,同时每个判断本身的所有可能判断结果至少执行一次。
例如,对于图例中的个判断,若 T1,T2,T3,T4 及 !T1,!T2,!T3,!T4 的含义如之前所属,则只需要设计以下两个测试便可覆盖图例的8个条件取值以及4个判断分支。
测试用例 | 通过路径 | 条件取值 | 覆盖分支 |
---|---|---|---|
【(2,0,4),(2,0,3)】 | ace(L1) | T1,T2,T3,T4 | c,e |
【(1,1,1),(1,1,1)】 | abd(L2) | !T1,!T2,!T3,!T4 | b,d |
判断-条件覆盖也有缺陷。从表面上看,它测试了所有条件的取值,但是事实并非如此。因为往往某些条件掩盖了另一些条件。
为彻底地检查所有条件的取值,可以将图例中的多重条件判定分解,形成如下图所示的由多个基本判断组成的流程图。这样可以有效地检查所有的条件是否正确。
2.1.5 条件组合覆盖
所谓条件组合覆盖就是设计足够的测试用例,运行被测程序,使得每个判断的所有可能的条件取值组合至少被执行一次。
2.1.6 路径覆盖
3 黑盒测试的测试用例设计
3.1 等价类划分
不可能用所有可以输入的数据来测试程序,而只能从全部可供输入的数据中选择一个子集进行测试。如何选择适当的子集,以便尽可能多的发现错误。解决方法之一就是等价类划分。
3.1.1 划分等价类
等价类是指某个输入域的子集合,在该子集合中,各个输入数据对于揭露程序中的错误都是等效的。
并合理的假设:测试某个等价类的代表值等价于对于这一类其他值的测试。
因此,可以把全部可供输入的数据合理划分为若干等价类,在每一个等价类中取一个数据作为测试的输入,这样就可以用少量代表性测试数据,达到测试的要求。
等价类的划分有两种不同的情况:
- 有效等价类:是指对于软件的规格说明来说,合理的、有意义的输入数据构成的集合。
利用它,可以检验程序中是否实现了规格说明预先规定的功能和性能。 - 无效等价类:是指对于软件的规格说明来说,不合理的、无意义的输入数据构成的集合。
程序员主要利用这一类测试用例检查程序中功能和性能的实现是否有不符合规格说明要求的情况。
在设计测试用例时,要同时考虑有效等价类和无效等价类。软件不能都只接受合理的数据,还要经受意外的考验,检验出无效的或不合理的数据,这样的软件测试才是全面性的。
以下结合具体实例给出几条划分等价类的原则:
- 如果输入数据规定了取值范围或值的个数,则可以确定一个有效等价类和两个无效等价类。
例如,在软件规格说明中,对输入数据有一句话:“……项数可以从1到999……”。则有效等价类是“1≦项数≦999”,两个无效等价类是“项数<1”和“项数>999”。 - 如果规定说明规定了数据值的集合,或者是规定了“必须如何的条件”,这时可确定一个有效等价类和一个无效等价类。
例如,在某程序语言中对变量标识符规定为“以字母打头的……串”。那么所有以字母打头的串构成有效等价类,而不在此集合内(不以字母打头)的串归于无效等价类。 - 如果规格说明中规定的是一个条件数据,则可确定一个有效等价类和一个无效等价类。
例如,“……成人(年满18岁)须……”,则考虑成人为一有效等价类,未满18岁者为无效等价类。 - 如果我们确知,已划分的等价类中各种元素在程序中的处理方式不同,则应将此等价类进一步划分成更小的等价类。
3.1.2 确定测试用例
在确定了等价类后,建立等价类表,列出所有划分出的等价类:
输入数据 | 有效等价类 | 无效等价类 |
---|---|---|
…… | …… | …… |
再从划分出的等价类中按以下原则选择测试用例:
- 为每一个等价类规定一个唯一的编号;
- 设计一个新的测试用例,使其尽可能多得覆盖尚未被覆盖的有效等价类,重复这一步,直到所有的有效等价类都被覆盖为止。
- 设计一个新的测试用例,使其仅覆盖一个尚未被覆盖的无效等价类,重复这一步,知道所有的无效等价类都被覆盖为止。
上述原则中,原则2完全是为了把测试工作量减到最小,原则3则尽可能的把多个错误分开。
3.1.3 用等价类划分法设计测试用例的实例
在某程序设计语言的语法中规定:“标识符是以字母开头,后面跟字母或数字的任意组合而构成的。有效字符数为8个,最大字符数80个。”并且规定:“标识符必须先说明,再使用。”“在同一说明语句中,标识符至少必须有一个”
依次,建立输入等价类表如下(表中括号中的数字为等价类编号):
输入数据 | 有效等价类 | 无效等价类 |
---|---|---|
标识符个数 | 1个(1),多个(2) | 0个(3) |
标识符字符数 | 1~80个(4) | 0个(5),>80个(6) |
标识符组成 | 字母(7),数字(8) | 非字母数字字符(9),保留字(10) |
第一个字符 | 字母(11) | 非字母(12) |
标识符使用 | 先说明后使用(13) | 未说明已使用(14) |
3.2 边界值分析
3.2.1 边界值分析方法的考虑
边界值分析是对等价类划分方法的补充。
通常输入等价类与输出等价类的边界是需要认真考虑的。应该选取正好等于、刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据。
3.2.2 选择测试用例的原则
- 如果输入数据规定了值的范围,则应取刚达到这个范围的边界的值,以及刚刚超越这个范围边界的值作为测试输入数据。
例如,若输入值的范围是“-1.0~1.0”,则可选取“-1.0”、“1.0”、“-1.001”、“1.001”作为测试输入数据。 - 如果输入数据规定了值的个数,则用最大个数、最小个数、比最大个数多1、比最小个数少1的数作为测试数据。
例如,一个输入文件有1~255个记录,设计测试用例时则可以分别设计有1个记录、255个记录以及0个记录和256个记录的输入文件。 - 根据规格说明的每个输出数据,使用前面的原则1。
例如,某程序的功能是计算折扣量,最低折扣量是0元,最高折扣量是1050元。则涉及一些测试用例,使其恰好产生0元和1050元的结果。 - 根据规格说明的每个输出数据,使用前面的原则2.
例如,一个信息检索系统根据用户输入的命令,显示有关文献的摘要,但最多只显示4篇摘要。这是可设计测试用例,使得程序分别显示1篇、4篇、0篇摘要,并设计一个有可能使程序错误地显示5篇摘要的测试用例。 - 如果程序的规格说明给出的输入域或输出域是有序集合(如有序表、顺序文件等),则应选取集合的第一个元素和最后一个元素作为测试用例。
- 如果程序中使用了一个内部数据结果,则应该选择这个内部数据结构的边界上的值作为测试用例。
例如,如果程序中定义了一个数组,其元素下标的下界是0,上界是100,那么应选择达到这个数组下标边界的值,如0与100,作为测试用例。 - 分析规格说明,找出其它可能的边界条件。
复习《软件工程概览》第10章的软件测试方法,记录摘要,总结。
2019-10-18