Ch 5. 戴上眼罩测试软件
本章描述最常用、最有效的软件测试技术,主要从黑盒测试的角度解释如何测试软件。
动态黑盒测试:戴上眼罩测试软件
不深入代码细节的测试方法是一种动态黑盒测试。
程序需要运行,软件测试员像用户一样使用它,因而是动态的;测试员不需要了解软件的内部结构,只需要关注软件的功能是否符合需求,这就是黑盒。
- 测试用例:进行测试时使用的特定输入、预期输出及测试的步骤。
永远牢记软件测试 第一条原则,完全测试程序是不可能的。
选择测试用 例是测试员最重要的一项任务。要准确评估风险,把无穷尽的可能性减少到可以控制的范围。
当采用大爆炸模式或边写边改模式时,能够供测试员参考的产品说明书可能并不完整或根本没有,这就需要测试员把软件本身当做产品说明书,开展 第四章 中的静态黑盒测试,一边了解软件的功能,一边再开展动态黑盒测试,较为系统地测试软件。
通过性测试和失效性测试
通过(Pass)和失效(Fail)是软件测试中最基本的两个结果。同样地,通过性测试和失效性测试是软件测试中最基本的两种测试方法。
- 通过性测试:测试软件是否符合需求,是否能够正常工作。重在检查正常情况下软件是否存在问题。
- 失效性测试(或错误强制测试):测试软件在异常情况下是否能够正常工作。重在检查软件是否能够容错。
总是应该先进行通过性测试,再进行失效性测试。
在用苛刻的极端条件测试软件前,测试员往往会发现软件在正常情况下就存在不少问题。
选择测试用例:等价类划分
等价类划分(Equivalence partitioning / classing)是一种常用的测试用例设计方法,它分步骤地将无限的测试用例缩减到有限的测试用例,却拥有等价的测试效果。
- 等价类:具有相同功能和特性的输入数据的集合,或是测试相同目标或软件缺陷的输入数据的集合。
- ⭐ 有效等价类:包含有效输入数据的等价类,用于通过性测试。
- ⭐ 无效等价类:包含无效输入数据的等价类,用于失效性测试。
划分等价类并没有固定的标准,取决于测试员对软件的理解和经验。
例如,测试员可能将某个功能的不同触发方式划分为数种等价类,而在对软件质量更有信心后,可能会将这些等价类合并。
也正是因为这种主观性,等价类划分也可能导致漏掉一些重要的测试用例。说到底,等价类划分仍然是一种不完全测试,不可避免地存在风险。
构造等价类后,应当从等价类中挑选典型的测试用例,保证有效等价类和无效等价类都能单独地覆盖。这就是说,不要选择两个等价类中的测试用例,因为这样的测试用例可能会导致测试结果的混淆。
数据测试
狭义的软件大体上可以分为两个部分:数据和程序(也就是处理数据的算法),由此测试也可以分为数据测试和程序测试。
数据测试所要面临的第一个难题就是数据的海量性——没错,第一原则 仍然在这里发挥作用——因此,开展等价类划分分而治之是必不可少的步骤。
下面给出一些常见的等价类划分原则,可以帮助测试员更好地构造数据测试用例。
选择测试用例:边界条件
边界条件是特殊情况。我们之所以选择边界条件,是基于以下的事实:编程从根本上就更容易在边界条件处出错。
这有点像归纳法的思想:如果一个程序在边界条件下能够正常工作,那么它在其他情况下也大概率能够正常工作。
边界条件类型
边界条件是指软件运行在计划操作界限的边界的情况。通常来说,下述特征属于边界条件:
- 第一个和最后一个;
- 最小和最大;
- 开始时和结束时。
- 空和满(注意不是非空,这是一般情况)。
- ……
考虑输入值的实际应用意义(例如数量、速度等)能够更快地找到边界条件。
想起一张 meme:
鸡兔同笼问题里算出共有 -1 只鸡的你 be like
测试边界
仅仅测试边界点是不够的,最好是还能测试边界的两侧:一边测试刚好超过边界的无效数据,另一边测试刚好在边界内的有效数据。
⭐ Paul C. Jorgensen 公式
设 为存在边界值的参数个数, 为边界值条件数,则有
- 公式:基本边界测试
- 每个参数取 ,, , 各一次, 同时其他参数取典型值 ;最后全部参数取典型值 ,额外记一次。
- 公式:健壮性边界测试
- 在基本边界测试基础上,为每个参数增加 和 的测试。
- 公式:边界条件测试
- 每个条件值 取 , , 各一次。
选择测试用例:次边界条件
上述的普通边界条件是很容易在产品说明书或软件的过程中找到的,然而有些边界条件却隐藏在软件内部,最终用户很难发现,软件测试员却必须对其进行测试。这就是次边界条件(Sub-boundary condition)或者内部边界条件(Internal boundary condition)。
换言之,次边界条件是软件的隐性边界条件。
次边界条件类型:数值
计算机内部,任何数据都是通过二进制数表示的,因此 2 的幂次方是一个重要的次边界条件,在等价划分时有时需要特别注意。
有些安全隐患就是由于程序员没有考虑到这些次边界条件而导致的,例如数值溢出()。
次边界条件类型:字符编码
无论是 ASCII 还是 Unicode,字符编码都有其特殊的边界条件。例如,ASCII 中的控制字符和可显示字符,Unicode 中的基本多文种平面和辅助多文种平面。
此外,还需要考虑用户合法输入在这些临界处的处理方式。例如,如果某个被测的文本框只能输入 ASCII 数字,那么就要考虑输入 /
(ASCII 47,在 0
前)和 :
(ASCII 58,在 9
后)的情况。
选择测试用例:默认、空白、空值、零值和无
Default, Empty, Blank, Null, Zero, and None
无论你如何称呼这种缺少用户输入的情形——不是错误输入也不是正确输入,而是什么都没有——它们都是软件测试中需要注意的一种情况。
好的软件会尝试提示用户错误输入,别的软件也会尝试自动填充某个默认值;当然,什么也不做直到内部逻辑抛出异常或者恐慌的依然大有“软件”在。
个人认为,自动填充默认值虽然给用户省了一些事,但是也可能会导致用户不知不觉地提交了错误的数据 。更要命的是,测试时可能漏掉软件错误地认为用户没有输入(实际上有输入)而自动填充的问题。
这同样告诉我们,默认值也并非解决一切空白输入问题的万能药。
由于这种情况下程序一般会执行不同的路径,因此最好不要将其与现有的有效或无效等价类划分在一起。
选择测试用例:垃圾数据
想在善和恶中作造物主的人,必须首先是个破坏者,并砸烂一切价值。也就是说,最大的恶属于最高的善。不过,后者是创造性的善。
- 垃圾数据:无意义、无效、不合理的数据,它们可能是用户输入错误、软件错误、或者是恶意的。
砸烂 一切!如果说上述的所有测试方法都是有迹可循的严谨技术的话,那么这里所介绍的就是纯粹的、混沌的破坏。
测试员必须假定用户(或更加中性地说,软件的最终使用者)不一定会遵循常人的思路,而是可能会故意或无意地输入一些不合理的数据。要知道,用户可不会责怪自己,错的一定是软件!
所以,在规规矩矩地完成上面的那些技术测试后,测试员也需要适当放松一下——搞搞破坏! 不必为此愧疚,测试员所做的一切都是为了让软件更加完善。
释放你创造力和想象力的时候到了!
- 软件要求输入数字?那就用字母和符号淹没它!
- 软件要求输入日期?那就试试千百年前的原始时代或者宇宙尽头来让它崩溃!
- 高精度操作要求?狂按键盘——别忘了,这是在测试容错性!
- ……
某种程度上,这和计算机安全领域的模拟攻防战是一致的。要想造出更加健壮和安全的软件,就必须不断地挑战它,让它能够抵御最恶劣的条件。
状态测试
当数据测试完成后,测试员需要考虑过不同的状态验证程序的逻辑流程。软件通过代码执行不同的分支,触发某些数据位,设置一些变量,读取一些数据,最终转入另外一个状态。
- 软件状态:软件当前所处的条件或者模式。
选择测试用例:状态转换图
尽管软件的状态理论上是可以穷尽的,但它随着变量增多而以不可思议的速度增长。因此,测试员需要简化这个状态空间,找出最重要的状态。
- 状态转换图:描述软件所能处于的状态以及状态之间的转换关系(条件)的图表。
这样的图表通常应该包含以下几个部分:
- 软件可能进入的每一种独立状态:与其他状态互不重叠的状态的集合。
- 从一种状 态到另一种状态所需的输入和条件:状态不可能无缘无故地发生转移,必然是由某些输入或条件触发的。
- 进入或者退出某种状态的设置条件及输出结果:实际上就是状态转移所带来的影响。
尽管这里似乎开始有些深入程序的运行逻辑,但这仍然是相对于用户来说的。想想这章的标题——戴上眼罩测试软件,我们现在仍然在黑盒测试的范畴内,因此只需要从客户的角度建立状态转换图即可。
为了缩减状态空间,测试员可以尝试以下方法:
- 单状态测试:每种状态至少访问一次,如何到达的没有关系,但是要确保每种状态都能够正常工作。
- 常见情况的转换:测试看起来最常见和普遍的转换(这些信息可以从对产品说明书的静态黑盒分析中获得)。
- 最不常见的转换:测试看起来最不常见的转换,这些转换可能会导致软件的崩溃或者错误。
- 测试错误状态和相关处理:错误常常没有被正确处理,或是处理程序之间缺乏有效的交互。
- 测试随机状态:随机选择一些状态进行测试,以确保软件能够在任何状态下正常工作。
⭐ 选择测试用例:因果图与判定表
对于软件,我们常有如下事实:
- 软件的输入和输出之间存在因果逻辑关系,可以用因果图(cause-effect diagram)刻画。
- 因果图可从产品说明书中获得。
因此,我们可以通过绘制因果图来帮助我们排除一些不必要的测试用例(永远不会发生的情况)。
因果图
我们使用 表示原因, 表示结果,原因与结果有4种关系:
- 恒等: 和 同时出现或同时不出现。
- 非: 出现时 不出现, 不出现时 出现。
- 或:当且仅当多个 中至少有一个出现时 出现。
- 与:当且仅当多个 同时出现时 出现。
除此以外,因果图还有 4 种输入约束:
- 互斥:多个原因不能同时成立,最多有且仅有一个能成立。
- 包含:多个原因中至少一个必须成立,最少有且必须有一个成立。
- 唯一:多个原因中有且仅有一个必须成立。
- 要求:当 成立时, 必须成立。
还有一种输出约束:
- 屏蔽:当 出现时, 不能出现;但当 不出现时, 可以出现。
判定表
根据因果图可以绘制出判定表(decision table),描述原因的所有组合及相应的结果组合。
通常判定表由四个部分组成:
- 条件桩:列出问题的所有条件,除了某些问题对条件的先后次序有要求之外,通常所列条件的先后次序都无关紧要。
- 条件项:条件项就是条件桩的所有可能取值。
- 动作桩:动作桩就是问题可能采取的操作,这些操作一般没有先后次序之分。
- 动作项:指出在条件项的各组取值情况下应采取的动作。
假设我们有如下产品说明书:
文件管理系统规格说明
- 文件第一列的字符必须是一个 A 或 B,且文件第二列的字符 必须是一个数字;
- 若符合上述情况,则打印文件已被修改的消息;
- 若第一个字符不正确,则打印 X12 消息;
- 若第二个不是数字,则打印 X13 消息;
我们可以列出如下原因和结果:
原因
- :第 1 列的字符是 A
- :第 1 列的字符是 B
- :第 2 列的字符是数字
结果
- :文件修改过
- :打印消息 X12
- :打印消息 X13
画出因果图:
则有判定表:
/ | 1 | 2 | 3 | 4 | 5 | 6 | |
---|---|---|---|---|---|---|---|
条件 | C1 | 0 | 0 | 0 | 0 | 1 | 1 |
C2 | 0 | 0 | 1 | 1 | 0 | 0 | |
C3 | 0 | 1 | 0 | 1 | 0 | 1 | |
行动 | A1 | 0 | 0 | 0 | 1 | 0 | 1 |
A2 | 1 | 1 | 0 | 0 | 0 | 0 | |
A3 | 1 | 0 | 1 | 0 | 1 | 0 |
对于没有任何输出行动的列,可以将其删去。
对于判定表中具有相同动作并且在条件项之间存在极大相似的多项,可以考虑将其合并,标记为 —
,以表示该条件项与取值无关。
失效性测试
除了上述的通过性测试外,测试时还需要找到使得被测软件失效的测试用例。这就是失效性测试。
常见的失效性测试包括以下几种:
- 竞争条件(Race Condition):多个线程或进程同时访问共享资源,导致数据不一致。共享资源可以是数据,也可以是与计算机相连的外围设备。
- 重复测试(Repitition Testing):多次重复相同的操作,看看软件是否能够正确处理。该测试主要在于暴露内存泄漏问题,通过反复访问软件的某个功能,看看软件是否会因为资源耗尽而崩溃。
- 压力测试(Stress Testing):使软件在不够理想的条件下运行,例如限制内存、CPU、网络等资源,目的是测试软件运行的最低必要条件。
- 重负测试(Load Testing):测试软件在最大负载下是否能够正常工作。这种测试通常用于测试软件的性能。对时间、速度的要求实际上也是一种重负测试。
开发团队可能会对在这种条件下对软件进行测试表示抱怨,认为这种测试是不公平的。但测试员必须坚守自己的立场,因为用户才是最终的评判者,而用户可不会因为软件在不够理想的条件下崩溃而对软件表示同情。
其他黑盒测试技术
通过数据测试和状态测试,已基本上能够覆盖软件的绝大多数功能。如果测试员希望进一步找出漏网之鱼的话,那么就可以参考以下这些有创造力的测试方法。
像笨拙的用户那样做
个人觉得,这应该成为任何软件开发或测试的金玉良言:
永远把你的用户当作笨蛋。
最大的重点就是抛开对软件的先入为主式的理解,像一个完全不懂软件的用户一样使用软件。如果可以的话,找几个真正的用户来测试软件,看看他们是如何使用软件的。
像黑客一样考虑问题
以子之矛,陷子之楯何如?其人弗能应也。
这是找出安全缺陷的最好方法。想想软件中有价值的地方在哪里,然后想想如何破坏它,最后想尽办法阻止你刚刚的想法。
小测验
判断是非:在没有产品说明书和需求文档的情况下,可以直接进行动态黑盒测试。
理论上不行,因为黑盒测试是建立在对需求的理解之上的,缺乏需求文档就缺乏了判断对错的标准。
但在实际情况下,测试员可能被迫面临这种情况而不得不直接进行动态黑盒测试,这被称为探索测试。虽然在较为紧张的时间下,探索测试可能是唯一的选择,但是这种测试方法绝对不是最佳选择。
如果测试程序向 打印机输送打印内容,应该选取哪些通用的失效性测试用例?
首先要明确是打印机本身被测试。
- 竞争条件:多个程序同时向打印机输送打印内容,看看打印机是否能够正确处理。
- 重复测试:多次重复打印相同的内容,看看打印机是否会出现问题。
- 压力测试:限制打印机的资源,看看打印机是否按预期不工作。例如不装墨盒、纸或是干脆不接电源。
- 重负测试:向打印机输送大量的打印内容,看看打印机是否能够正常工作。
Windows 自带打印窗口中的打印区域存在什么样的边界条件?
打印窗口
第一页和最后一页,以及该范围内的 的幂次方都是边界条件。尝试 0 或负数页,或者超过最大页数的页数,它们应当都是无效的输入。
假设有一个文本框要求输入 10 个数字(第 5 位后带连字符)的邮政编码,对于该文本框应该进行怎样的等价划分?
- 有效等价类:10 个字符的邮政编码,满足数字和连字符的要求。
- 无效等价类
- 连字符不在第 5 位后的邮政编码。
- 缺少连字符的邮政编码。
- 多于 1 个连字符的邮政编码。
- 非数字的邮政编码。
- 少于 10 个数字的邮政编码。
- 少于 5 个数字的邮政编码。
- 空输入。
- ……