软件测试的艺术
  Pq37jUF4UeqZ 2023年11月02日 37 0

@Author: Basil Guo @Date: Apr. 7, 2021 @Description: 读书笔记,软件测试的艺术 @Keyword: test

0. 简介

这本书应该是每个测试人员必备的,算是软件测试的最原始思想沉淀,在经过了40多年的软件技术发展,工具、语言的变更,这本书种所提及的测试思想却一直没有变,怪不得有人称该书为测试领域的"开山鼻祖",作者Glenford J. Myers提出的软件测试的概念,至今仍被很多测试数据所引用。

1. 一次自评价的测试

判断三角形形状的测试用例,三角形形状有:不规则三角形、等腰三角形、等边三角形。

  1. 有效的不规则三角形 | 等腰三角形 | 等边三角形
  2. 三个测试用例,是等腰三角形,但是是3个全排列(如(334, 343, 433))。
  3. 不可构成三角形的测试用例:0、负数,两边之和小于等于第三边(336,633,363;236,263,326,362,623,632)
  4. 异常值(000,非整数值(小数、字符))
  5. 测试用例需要有预期输出

2. 软件测试的心理学和经济学

测试:为发现程序错误而执行程序的过程。测试就是要尽可能地发现错误。

黑盒测试:又称为数据驱动的测试或输入/输出驱动地测试,使用时将程序视为一个黑盒子,**测试的目标与程序内部机制和结构无关,而是关注于发现程序不按其规范正确运行地环境条件。测试使用的数据完全来自于软件规范,完全测试,需要穷举所有的输入测试,将所有的可能的输入条件都作为测试用例。<span style="color: blue;">穷举输入测试</span>**就是不经济的,完全不可实现。以第一章的例子,要穷举所有整数范围的值才行。大规模程序的测试用例更多。

白盒测试:逻辑驱动的测试,允许我们检查程序的内部结构。这种测试策略对程序的逻辑结构进行检查,从中获取测试用例,但常会忽略程序的规范。测试的目标是针对白盒测试,建立起与黑盒测试中穷举输入测试相似的测试方法。最简单的方式是使得每条语句执行一次,即**<span style="color: blue;">穷举路径测试</span>**,但这还不够,而且所有路径合计可能是一个天文数字。

  1. 即使穷举所有路径测试,也不能保证程序符合其设计规范
  2. 程序可能缺少或增多了某些路径而存在错误;
  3. 穷举路径测试可能不会暴露数据敏感错误,即程序逻辑有误,可能输出无误。

<span style="color: red;">软件测试的原则</span>

  1. **测试用例中一个必需部分是对预期输出或结果进行定义。**一个测试用例必须包括两个部分:对程序的输入数据的描述,对程序在上述输入数据下的正确输出结果的精确描述。
  2. 程序员应当避免测试自己编写的程序。**编写是建设性的,测试是破坏性的。**但测试不是调试,调试还是应该由程序员来。
  3. 编写软件的组织不应当测试自己编写的软件。可由客观独立的第三方来做(专门的测试团队)。
  4. 应当彻底检查每个测试的执行结果。
  5. 测试用例的编写不仅应当根据有效和预料到的输入情况,而且也应当根据无效和未预料到的输入情况。
  6. 检查程序是否“未做到其应该做的”仅是测试的一半,测试的另一半是检查程序是否“做了其不应该做的”。
  7. 应该避免测试用例用后即弃,除非软件本身就是一个一次性的软件。
  8. 计划测试工作时不应默许假定不会发现错误。
  9. 程序某部分存在跟多错误的可能性,与该部分已发现错误的数量成正比。残存错误和已知错误成正比。
  10. 软件测试是一项极富创造性、极具智力挑战性的工作。

3. 代码检查、走查和评审

研读程序代码作为测试工作的一部分,已经得到了广泛的认同。代码检查、走查以及可用性测试是三种主要的人工测试方法。

3.1 代码检查

以组为单位阅读代码,它是一系列规程和错误检查技术的集合。代码检查小组通常有4人,其中一人协调,不是程序代码作者,是程序员,分发材料,安排进程,记录错误,确保后续改正。

检查时主要是代码作者逐条语句讲述程序逻辑结构,其它小组成员提问题、判断是否存在错误,核对用于代码检查的错误列表,不要太注重编码风格(这是的IDE的活),不要太模糊,经验之谈:

  1. 数据引用错误:未赋值或未初始化的变量,数组越界,数组下标都是整数,指针或引用变量的引用,是否已分配内存空间,别名应用时内存区域中数据值要有正确的属性,变量类型或属性或内存属性是否与编译器所预期的一致,直接寻址/间接寻址错误,多个函数中引用的数据结构是否有一致的定义,OOP语言是否所有继承都在实现类中得以满足;
  2. 数据声明错误:变量需要明确的声明,变量默认属性是否被正确理解,变量在声明语句中是否被正确初始化,每个变量都有正确的长度和数据类型,变量初始化和存储空间是否类型一致,相似名称的变量可能发生混淆;
  3. 运算错误:不一致数据类型变量之间的运算,混合数据类型的运算,相同数据类型不同字长变量运算,赋值语句目标变量的数据类型是否小于右边表达式的数据类型或结果,表达式中的上溢和下溢情况,除数为0,二进制的精度,变量值的溢出,运算优先级;
  4. 比较错误:不同数据类型变量比较,混合模式比较运算,运算符使用正确与否,布尔表达式正确与否,布尔运算符的操作数是否是布尔类型,浮点数数之间的比较,运算符的优先级顺序,编译器对布尔表达式的计算方式;
  5. 控制流错误:多分支程序中可能结果大于分支总数,所有循环最终终止,程序、模块、子程序最终终止,循环入口条件得以满足,循环中越界处理或仅差/多一个的错误,是否存在不能穷尽的判断;
  6. 接口错误:被调用方法/函数的形参数量与实参数量匹配、顺序正确、属性匹配、量纲匹配,型参与实参关系(改变形参对实参的影响),全局变量的使用;
  7. 输入输出错误:文件明确声明且属性正确,打开文件的各项属性设置正确,格式规范I/O语句中信息吻合,足够内存空间以打开程序,文件使用前打开,文件使用后关闭,文件结束判断与处理,对I/O出错情况处理是否正确,打印或显示的文本信息中是否存在拼写或语法错误,对于不存在文件的处理;
  8. 其它检查:程序的robust性,程序功能是否完全满足,编译器警告和提示信息。

每小时约150行,每次一个或几个子模块。

3.2 代码走查

代码走查时,一组开发人员对代码进行审核,其中一人是代码作者。代码走查也是会议形式。参会人员需要把每个测试用例沿着程序逻辑结构走一遍,记录程序的状态,但是这些测试用例需要尽可能地少且简单,仅仅是为了检查程序的逻辑。

3.3 桌面检查

古老方法,单人进行,阅读程序,对照错误列表,对程序进行推演。低效无约束,而且一般本工作由程序员自己完成,违反了测试原则。

3.4 同行评审

4. 测试用例设计

软件测试的最关键问题是:在所有可能的测试用例中,哪个子集最有可能发现最多的错误。

随机输入测试比较低效。而穷举的黑盒和白盒测试通常不可能,建议两者组合起来得到一种合理的测试策略。

黑盒测试 白盒测试
等价类划分 语句覆盖
边界值分析 判定覆盖
因果图分析 条件覆盖
错误猜测 判定/条件覆盖
多重条件覆盖

4.1 白盒测试

逻辑覆盖测试

  • 语句覆盖通过编写单个的测试用例遍历程序路径,可以执行到每条语句。这条准则有很大的不足,以至于它通常没什么用处。

  • 判定覆盖或者分支覆盖是较强一些的逻辑覆盖准则。该准则要求必须编写足够的测试用例,使得每一个判断都至少有一个为真和为假的输出结果。即每条分支路径都必须至少遍历一次。判定覆盖通常可以满足语句覆盖。判定覆盖要求每个判断都必须有“是”和“否”的结果,并且每条语句都至少被执行一次。

  • 条件覆盖更强一些,要编写足够的测试用例以确保将一个判断中的每个条件的所有可能的结果至少执行一次,因为就如同判定覆盖的情况一样,这并不总是能让每条语句都执行到,因此作为对这条准则的补充就是对程序或子程序的每一个入口点都至少调用一次。

  • 判定/条件覆盖用于解决条件覆盖不能覆盖到的情况,这种准则要求设计出充足的测试用例,将一个判断中的每个条件的所有可能的结果至少执行一次,将每个判断的所有可能的结果至少执行一次,每个入口点都至少调用一次。但是常常不能全部执行到所有的条件,某些条件可能被屏蔽。

4.2 黑盒测试

  • 等价划分,因为无法穷举,需要努力从所有可能输入中找出某个小的子集。将输入将程序输入范围进行划分,将其划分为有限数量的等价类,这样就可以合理的假设测试每个等价类的代表性数据等同于测试该类的其他任何数据。有效等价类代表对程序的有效输入,无效等价类则代表其它任何可能的不正确的输入值。

  • 边界值分析,边界条件,是指输入和输出等价类中那些恰好处于边界、或超过边界、或在边界以下的状态。边界值分析方法和等价划分之间的重要区别是,边界值分析考察正处于等价划分边界或在边界附近的状态。

  • 因果图分析,边界值分析和等价划分的一个弱点是未对输入条件的组合进行分析。因果图是一种形式语言,用自然语言描述的规格说明可以转换为因果图。

  • 错误猜测

4.3 测试策略

一组合理的策略如下:

  1. 如果规格说明中包含输入条件组合的情况,应首先使用因果图分析方法。

  2. 在任何情况下都应使用边界值分析方法。应记住,这是对输入和输出边界进行的分析。边界值分析可以产生一系列补充的测试条件,但是,也正如“因果图分析”一节所述,多数甚至全部条件都可以被整合到因果图分析中。

  3. 应为输入和输出确定有效和无效等价类,在必要情况下对上面确认的测试用例进行补充。

  4. 使用错误猜测技术增加更多的测试用例。

  5. 针对上述测试用例集检查程序的逻辑结构。应使用判定覆盖、条件覆盖、判定/条件覆盖或多重条件覆盖准则(最后的一个最为完整)。如果覆盖准则未能被前四个步骤中确定的测试用例所满足,并且满足准则也并非不可能(由于程序的性质限制,某些条件的组合也许是不可能实现的),那么增加足够数量的测试用例,以使覆盖准则得到满足。

5. 模块(单元)测试

模块测试是对程序中的单个子程序、子程序或过程进行测试的过程。模块测试的目的不是证明模块能够正确地运行,而是证明模块中存在着错误。

5.1 测试用例设计

在为模块测试设计的测试用例时,需要使用两种类型的信息:模块的规格说明模块的源代码。模块测试总体上是面向白盒测试的。使用一种或多种白盒测试方法分析模块的逻辑结构,然后使用黑盒测试方法对照模块的规格说明以补充测试用例。

5.2 增量测试

这里讨论将模块组装成工作程序的方式。两种增量测试:自顶而下的和自底而上的开发或测试过程。

软件测试是否应先独立地测试每个模块,然后再将这些模块组装成完整的程序?还是先将下一步要测试的模块组装到测试完成的模块集合中,然后再进行测试?第一种方法称为非增量测试或“崩溃(big-bang)”测试,而第二种方法称为增量测试或集成。

测试单独的模块需要一个特殊的驱动模块(driver module)和一个或多个桩模块(stub module)。驱动模块是人们编写的一个小模块,用来将测试用例驱动或传输到被测模块中(也可以用测试工具替代)。桩模块接受某个模块的调用指令,模拟另一个模块的功能的模块。这种测试单独的模块的方式称之为非增量测试

增量测试首先将下一个要测试的模块组装到前面已经测试过的模块集合中去。增量测试优于非增量测试。

5.3 自顶向下测试与自底向上测试

“自顶向下的测试”和“自顶向下的开发”确实是同义词(表示安排模块的编码和测试顺序的策略),但“自顶向下的设计”则完全不同并且是独立的概念,按自顶向下模式设计的程序既可使用自顶向下的方式,也可使用自底向上的方式进行增量测试。自底向上的测试是一种增量测试。

在大多数情况下,自底向上的策略与自顶向下的策略是相对立的;自顶向下测试的优点成为自底向上测试的缺点,而自顶向下测试的缺点又成为自底向上测试的优点。

自顶向下的测试是从程序的顶部或初始模块开始。测试开始之后,挑选哪一个后续模块进行增量测试惟一的原则是:要成为合乎条件的下一个模块,至少一个该模块的从属模块(调用它的模块)事先经过了测试。上级模块测试完成之后,就用一个实际的模块代替其中的一个桩模块(下级模块),而该下级模块需要的桩模块也被添加进来。**如果程序中存在关键部分,那么在设计模块序列时就应将这些关键模块尽可能早地添加进去。在设计模块序列时,应将I/O模块尽可能早地添加进来。**实践中时常会发生的一个终极问题是,在进行到下一个模块前未能穷举测试此模块。这来自于两个原因:一是由于将测试数据嵌入桩模块中存在困难,二是由于程序的较高层次通常会为较低层次提供资源。

  • 优点
    1. 如果主要的缺陷发生在程序的顶层将非常有利;
    2. 一旦引入I/O功能,提交测试用例会更容易;
    3. 早期的程序框架可以进行演示,并可激发积极性。
  • 缺点
    1. 必须开发桩模块;
    2. 桩模块要比最初表现得更加复杂;
    3. 在引入I/O功能之前,向桩模块中引入测试用例比较困难;
    4. 创建测试环境可能很难,甚至无法实现;
    5. 观察测试输出很困难;
    6. 使人误解设计和测试可以交迭进行;
    7. 会导致特定模块测试的完成延后。

自底向上的测试开始于程序中的终端模块(此类模块不再调用其他任何模块)。测试完这些模块之后,同样没有最佳的方法来挑选要进行增量测试的下一个模块;惟一正确的原则是,要成为合乎条件的下一个模块,该模块所有的从属模块(它调用的模块)都已经事先经过了测试。每一模块都需要一个特殊的驱动模块:即包含着有效的测试输入、调用被测模块且将输出显示出来(或将实际输出与预期输出作比较)的模块。

  • 优点
    1. 如果主要的缺陷发生在程序的底层将非常有利;
    2. 测试环境比较容易建立;
    3. 观察测试输出比较容易。
  • 缺点
    1. 必须开发驱动程序;
    2. 直到最后一个模块添加进去,程序才形成一个整体。

6. 更高级别的测试

模块测试的目的是发现程序模块与其接口规格说明之间的不一致。功能测试的目的是为了证明程序未能符合其外部规格说明。系统测试的目的是为了证明软件产品与其初始目标不一致。

img

集成测试往往并不作为一个独立的测试步骤,而且在进行增量模块测试时,它是模块测试的隐含部分。

6.1 功能测试

功能测试是一个试图发现程序与其外部规格说明之间存在不一致的过程。外部规格说明是一份从最终用户的角度对程序行为的精确描述。功能测试通常是一项黑盒操作。也就是说,要依赖早期的模块测试的过程来实现理想的白盒逻辑覆盖准则。

黑盒测试中的等价类划分、边界值分析、因果图分析、错误猜测的方法都适用于功能测试。

6.2 系统测试

系统测试有着特定的目的:将系统或程序与其初始目标进行比较。

系统测试并不局限于系统。根据定义,如果产品没有一组书面的、可度量的目标,系统测试也就无法进行。

在寻找程序与其目标之间的不一致的过程中,应重点注意那些在设计外部规格说明的过程中所犯的转换错误。

测试用例分类

分类 说明
能力测试 确保程序的目标功能实现。不同于功能测试,能力测试的过程是逐条语句地检查目标文档,当某条语句定义了一个“要做什么”,就判断程序是否满足。
容量测试 发现处理大容量数据时的程序异常。容量测试的目的是为了证明程序不能处理目标文档中规定的数据容量。
强度测试 发现在大规模负载、高强度不间断持续的数据处理中的异常。所谓高强度是指在很短的时间间隔内达到的数据或操作的数量峰值。
可用性测试 又叫用户体验测试。评估最终用户在使用软件并与软件交互时的可用性问题。
安全性测试 试图攻破程序的安全防线
性能测试 评估程序的响应时间以及吞吐量瓶颈
存储测试 确保程序可以正确处理其对存储的需求,包括系统的存储和物理上的存储
配置测试 检查程序是否能在推荐配置上流畅运行。至少应该使用每一种类型的设备,以最大和最小的配置来测试程序。
兼容性/转换测试 评估新版本是否能兼容老的版本
安装测试 确保能够在所有支持的平台上安装软件
可靠性测试 评估程序是否能够达到规格说明中的运行时常和MTBF(平均故障间隔时间)要求。
可恢复性测试 测试系统恢复相关的功能是否按设计要求实现。设计目标之一是使平均恢复时间(MTTR)最小。
服务/可维护性测试 评估系统是否拥有良好的数据处理和日志机制,以备技术支持和调试之需
文档测试 校验所有的用户文档是否正确
过程测试 对软件系统操作或维护所需要涉及的流程进行评估和确定

系统测试执行中一个最关键的考虑是决定由谁来进行测试。我们从反面来回答这个问题:

  1. 不能由程序员来进行系统测试;
  2. 在所有的测试阶段之中,这是惟一一个明确地不能由负责该程序开发的机构来执行的测试。

6.3 验收测试

验收测试是将程序与其最初的需求及最终用户当前的需要进行比较的过程。这是一种不寻常的测试类型,因为该测试通常是由程序的客户或最终用户来进行,一般不认为是软件开发机构的职责。

尽管从原则上来讲验收测试是客户和最终用户的职责,但是明智的开发者会引导客户在开发过程和产品发布之前进行用户测试。

6.4 安装测试

安装测试与所有其他测试过程不同,与设计过程中的任何阶段都没有联系,其目的不是为了发现软件中的错误,而是为了发现在安装过程中出现的错误。

  1. 用户必须选择大量的选项;
  2. 必须分配并加载文件和库;
  3. 必须进行有效的硬件配置;
  4. 软件可能要求网络联通,以便与其他软件连接。

安装测试应由生产软件系统的机构来设计,作为软件的一部分来发布,在系统安装完成之后进行。除此之外,测试用例需要检查以确认已选的选项集合互不冲突,系统的所有部件全部存在,所有的文件已经创建并包含必需内容,硬件配置妥当等。

6.5 测试计划与控制

  1. 测试的目标和对象
  2. 测试结束准则(测试范围)
  3. 测试进度安排
  4. 测试人员责任。
  5. 测试用例和标准
  6. 测试工具
  7. 计算机时间
  8. 硬件配置
  9. 集成。系统集成计划规定了系统集成的顺序、系统每个版本的功能以及编写“脚手架”代码以模拟不存在的部件的职责分工。
  10. 跟踪步骤
  11. 调试步骤
  12. 回归测试。回归测试在对程序作了功能改进或进行了修改之后进行,其目的是判断程序的改动是否引起了程序其他方面的退步。回归测试通常重新执行测试用例中的某个子集。

6.6 测试结束准则

最常见的两个准则是:

  1. 用完了安排的测试时间后,测试便结束。
  2. 当执行完所有测试用例都未发现错误,测试便结束。也就是说,当所有的测试用例不成功时便结束。

7. 可用性测试/用户(体验)测试

基本上可用性测试属于黑盒测试的范畴。关注用户的体验,考虑用户群体的理解力、受教育程度以及所处环境?程序的输出是否有歧义甚至歧视侮辱?错误提示是不是只有程序员才能看懂?用户界面的连贯、一致,符合约定俗成?验证用户的合法性及验证其输入?精简用户使用成本,选项设置合理,符合人类思维直觉?及时响应用户输入,高亮用户选择?操作简单易上手,对不合法输入有准确无误的描述?用户随机切换功能和菜单是否发生紊乱?是否达到设计规格要求?

在测试开始前需要先向用户提供一份完整详细的使用说明。针对某一特定群体或者行业的软件开发需要由经验丰富的测试专家进行测试,这些专家通常具备这一类型软件的实际使用经验。相比较而言,针对适用范围更广的软件(如移动设备应用或者大多数互联网的网站系统),最好随机选择测试用户(这种选择测试用户的方式有时称为长廊测试或者长廊拦截测试,意即这种选择方式就如同在经过长廊的路人中随机抓取一批)。

基于Jakob Nielsen的研究成果,所需的测试人数可能比你想象得要少。Nielsen的研究发现,测试中找到的可用性问题数量可以用数学公式表示如下:$E=100×(1-(1-L)^n)$,其中:

  • E=找到错误的比例
  • n=测试人数
  • L=单个测试人员发现的可用性问题比例

检查程序是否“未做其应该做的”仅是测试的一半,测试的另一半是检查程序是否“做了其不应该做的”。

8. 调试

调试是执行一次成功的测试之后所要进行的工作。所谓成功的测试,是指它可以证明程序没有实现预期的功能。

  1. 暴力法调试

    1. 利用内存信息输出来调试。
    2. 根据一般的“在程序中插入打印语句”建议来调试。
    3. 使用自动化的调试工具进行调试。
  2. 归纳法调试:从特殊到一般

    归纳法调试的过程

  3. 演绎法调试:从一般到特殊

    演绎法调试的过程

  4. 回溯法调试:在小型程序中定位错误的一种有效方法是沿着程序的逻辑结构回溯不正确的结果,直到找出程序逻辑出错的位置。

  5. 测试法调试:供测试的测试用例,其目的是暴露出以前尚未发现的错误;供调试的测试用例,其目的是提供有用的信息,供定位某个被怀疑的错误之用。

9. 敏捷开发模式下的测试

敏捷测试是协同测试的一种形式,它要求每一个人都参与到测试计划的设计、实现以及执行中去。和敏捷开发的大多数特征无异,敏捷测试需要客户尽早参与到开发周期中来并一直到其结束。

敏捷测试依赖于自动化测试。因为开发周期很短,所以时间宝贵,而自动化测试相比较人工测试更可靠。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
Pq37jUF4UeqZ
最新推荐 更多