《软件测试的艺术》读后总结

近段时间一直在读《软件测试的艺术(原书第2版)》这本书,想看看经典的著作究竟如何讲测试。读完后总体上的感觉是“不错”,和读过的其他测试书籍讲的差不多,可能更简洁些,但还算很全面。这本新版里也增加了对新兴web测试的描述,以及对极限编程方法中如何进行测试也有涉及。其中提及的各种测试技术简单易懂,能否产生效用更多的依赖于使用者的深入理解和灵活应用的能力。

摘录概要内容如下:

第二章 软件测试的心理学和经济学

  • 测试的定义:测试是为发现错误而执行程序的过程。
  • 软件测试的重要原则:
    1. 测试用例中一个必需部分是对预期输出或结果的定义。
    2. 程序员应当避免测试自己编写的程序。
    3. 编写软件的组织不应当测试自己编写的软件。
    4. 应当彻底检查每个测试的执行结果。
    5. 测试用例的编写不仅应当根据有效和预期的输入情况,而且也应当根据无效和未预料到的输入情况。
    6. 检查程序是否“未做其应该做的”仅是测试的一半,测试的另一半时检查程序是否“做了其不应该做的”。
    7. 应避免测试用例用后即弃,除非软件本身就是一个一次性的软件。
    8. 计划测试工作时不应默许假定不会发现错误。
    9. 程序某部分存在更多错误的可能性,与该部分已发现错误的数量成正比。
    10. 软件测试是一项极富创造性、极具智力挑战性的工作。

第三章 代码检查、走查与评审

大多数的软件项目都应使用到以下的人工测试方法:

  • 利用错误列表进行代码检查
  • 小组代码走查
  • 桌面检查
  • 同行评审

第四章 测试用例的设计

  • 由于时间和成本的约束,软件测试的最关键问题是:在所有可能的测试用例中,哪个子集最有可能发现最多的错误?
  • 黑盒测试:等价类划分、边界值分析、因果图分析、错误猜测
  • 白盒测试:语句覆盖、判定覆盖、条件覆盖、判定/条件覆盖、多重条件覆盖

第五章 模块(单元)测试

模块测试(或单元测试)是对程序中的单个子程序、子程序或过程进行测试的过程,也就是说,一开始并不是对整个程序进行测试,而是首先将注意力集中在对构成程序的较小模块的测试上面。这样做的动机有三个。

  1. 首先,由于模块测试的注意力一开始集中在程序的较小单元上,因此它是一种管理组合的测试元素的手段。
  2. 其次,模块测试减轻了调试(准确定位并纠正某个已知错误的过程)的难度,这是因为一旦某个错误被发现出来,我们就知道它在哪个具体的模块中。
  3. 模块测试通过为我们提供同时测试多个模块的可能,将并行工程引入软件测试中。

模块测试的目的是将模块的功能与定义模块的功能规格说明或接口规格说明进行比较。这里的测试目标不是为了说明模块符合其规格说明,而是为了揭示出模块与其规格说明存在着矛盾。

第六章 更高级别的测试

  • 功能测试:试图发现程序与其外部规格说明之间存在不一致的过程。外部规格说明是一份从最终用户的角度对程序行为的精确描述。
  • 系统测试:最容易被错误理解,也是最困难的测试过程。系统测试并非是测试整个系统或程序功能的过程,因为有了功能测试,这样会显得多余。系统测试有着特定的目的“将系统与其初始目标进行比较”。给定这个目标之后,隐含两方面的含义:(1)系统测试并不局限于系统。如果产品是一个程序,那么系统测试就是一个试图说明程序作为一个整体式如何不满足其目标的过程;(2)根据定义,如果产品没有一组书面的、可度量的目标,系统测试也就无法进行。
    设计测试用例时应考虑全部的15种类型:
    1. 能力测试(facility testing):判断目标文档提及的每一项能力是否都确实已经实现。
    2. 容量测试:使程序经受大容量数据的检验。
    3. 强度测试:使程序承受高负载或强度的检验,高强度是指在很短的时间间隔内达到的数据或操作的数量峰值。
    4. 易用性测试:系统测试的另一个重要类型是试图发现人为因素或易用性的问题。
    5. 安全性测试:设计测试用例来突破程序安全检查的过程。
    6. 性能测试:很多软件都有特定的性能或效率目标,描述为在特定负载或配置环境下程序的响应时间和吞吐率。应设计测试用例来说明程序不能满足其性能目标。
    7. 存储测试:证明软件的存储目标没有得到满足。
    8. 配置测试:多种硬件配置或可运行的多种操作系统等。
    9. 兼容性/配置/转换测试:与现有系统的兼容以及从现有系统的转换过程。
    10. 安装测试:测试安装过程。
    11. 可靠性测试:所有类型的测试都是为了提高软件的可靠性,但如果软件的目标中包含了对可靠性的特别描述,就必须设计专门的可靠性测试。可能会有关于“平均故障间隔时间(MTBF)”的目标。
    12. 可恢复性测试:系统如何从程序错误、硬件失效和数据错误中恢复过来的机制。
    13. 适用性测试:适用性或可维护性的目标,可能定义了系统提供的服务辅助功能。
    14. 文档测试:系统测试也需要检查用户文档的正确性。
    15. 过程测试:很多软件都是较大系统的组成部分,这些系统并不完全是自动化的,包含了很多人员操作过程。在系统测试中,必须对所有已规定的人工过程进行测试。
  • 验收测试:将程序与其最初的需求及最终用户当前的需求进行比较的过程。通常是由程序的客户或最终用户来进行,一般不认为是软件开发机构的职责。
  • 安装测试:目的的是为了发现在安装过程中出现的错误。应由生产软件系统的机构来设计,作为软件的一部分来发布,在系统安装完成之后进行。
  • 测试的计划与控制:一个良好的测试计划应该包括“目标、结束准则、进度、责任、测试用例库及标准、工具、计算机时间、硬件配置、集成、跟踪步骤、调试步骤、回归测试”。

第七章 调试

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

虽然调试对于程序测试来说非常必要、不可或缺。但它似乎是软件开发过程中最不受程序员欢迎的部分之一。其主要原因可能包括以下几点:个人自尊会从中阻扰、热情耗尽、可能会迷失方向、必须自力更生。

  • 暴力法调试

这种方法之所以流行,是因为它不需要过多思考,是耗费脑力最少的方法,但同时也效率低下,通常来讲不是很成功。至少可以被划分为三种类型:

    1. 利用内存信息输出来调试
    2. 根据一般的“在程序中插入打印语句”建议来调试
    3. 利用自动化的调试工具进行测试

这些暴力调试方法的主要问题在于:它们都忽略了思考的过程。我们可以在调试程序和侦破谋杀案之间找出相似点来。实际上,在几乎所有的谋杀悬念小说中,谜案都是通过仔细分析线索,讲表面上不重要的细节全联结起来而最终侦破的。这不是一个使用蛮力的方法,要使用蛮力的是寻觅障碍物或搜寻财宝。

还有一些证据表明,无论调试小组成员是富有经验的程序员还是学生,肯动脑筋而不是依赖别人帮助的人能够更快、更准确地发现程序错误。因此,我们建议仅在下列情况下使用暴力调试方法:(1)其他的方法都失败了;(2)作为我们下面将会讨论的思考过程的补充,而不是替代方法。

  • 归纳法调试

归纳是一种特殊的思考过程,可以从细节转到全局,也即是从线索(即错误的症状,可能是一个或多个测试用例的结果)出发,寻找线索之间的联系。

归纳调试的步骤如下:

  1. 确定相关数据:列举出所有知道的程序执行的正确和不正确之处,那些相似却不相同、且未引起症状出现的测试用例提供了额外的有价值的线索。
  2. 组织数据:组织这些相关数据,以便观察线索间的模式,尤其重要的是找到矛盾、时间。
  3. 作出假设:研究线索之间的联系,利用线索结构里可能的模式作出一个或多个关于错误原因的假设。
  4. 证明假设:应将假设与其最初的线索或数据相比较,以此来证明假设的合理性,确定这些假设可以完全解释这些线索的存在。如果无法解释,要么这些假设是无效的或不完整的,要么还有更多的错误存在。
  • 演绎法调试

演绎的过程是从一些普遍的理论或前提出发,使用排除和精炼的过程,达到一个结论(错误的位置)。

演绎的步骤如下:

  1. 列举出所有可能的原因或假设:建立一份所有想象得到的错误线索的清单,线索不需要有完整的解释;它们纯粹是一些推测,帮助我们组织和分析现有的数据。
  2. 利用数据排除可能的原因:详细检查所有的数据,尤其寻找存在矛盾的地方,然后尽量排除所有可能的原因,仅留下一条。
  3. 提炼剩下的假设:此时的可能原因也许是正确的,但可能不够具体,不能指出错误来。因此,需要使用现有的线索来提炼这个准则。
  4. 证明剩下的假设。
  • 回溯法调试

沿着程序的逻辑结构回溯不正确的结果,直到找到程序逻辑出错的位置。也即,从程序产生不正确结果的地方开始,从该处观察到的结果推断出程序变量应该是些什么值。在头脑中,从这个位置开始逆向执行程序,重复使用“如果程序在此处的状态时这样的,那么程序在上面位置的状态就必然是那样的”过程,就能很快定位出错误。

  • 测试法调试

最后一个“思维型”的调试方法是使用测试用例。考虑下面两种类型的测试用例:供测试的测试用例,其目的是暴露出以前尚未发现的错误;供调试的测试用例,其目的是提供有用的信息,供定位某个被怀疑的错误之用。换句话说,当发现了某个被怀疑的错误的症状之后,我们需要编写与原先有所变化的测试用例,尽量确定错误的位置。

  • 调试的原则
    • 定位错误的原则
      1. 动脑筋
      2. 如果遇到了僵局,就留到稍后解决
      3. 如果遇到了困境,就把问题描述给其他人听
      4. 仅将测试工具作为第二种手段
      5. 避免使用试验法 —— 仅将其作为最后的手段
    • 修改错误的技术
      1. 存在一个缺陷的地方,很有可能还存在其他缺陷
      2. 应纠正错误本身,而不仅仅是其症状
      3. 正确纠正错误的可能性并非100%
      4. 正确修改错误的可能性随着程序规模的增加而降低
      5. 应意识修改错误会引入新错误的可能性
      6. 修改错误的过程也是临时回到设计阶段的过程
      7. 应修改源代码,而不是目标代码
  • 错误分析

调试除了有消灭程序中错误的价值之外,还有其他重要作用:它可以告诉我们软件错误的一些本质,我们对此了解得非常之少。关于软件错误本质的信息可以为改进将来的设计、编码和测试过程提供有价值的反馈信息。任何程序员或编程机构都可以从详细分析发现的错误,或至少一部分错误的过程中获得提高。详细的错误分析会包括如下内容:

  • 错误出现在什么地方?
  • 谁制造了这个错误?(不是为了处罚某人,而是为了进行培训)
  • 哪些做得不正确?
  • 如何避免该错误的出现?
  • 该如何更早的发现错误?

分析的过程是很艰难的,但是找到的答案为改进后续的编程实践提供极其宝贵的价值。

第八章 极限测试

为了满足XP的流程和思想,开发人员使用了极限测试方法,该方法强调连续测试。极限测试主要由两种类型的测试组成:单元测试和验收测试。

  • 极限单元测试

有两个简单规则:所有代码模块在编码开始之前必须设计好单元测试用例,在产品发布之前必须通过单元测试。

在开始编码之前设计单元测试所带来的一些好处:

  • 获得了代码将满足其规格说明的信心
  • 在开始编码之前,就展现了代码的最终结果
  • 更好地理解了应用程序的规格说明和需求
  • 可以先实现一些简单的设计,稍后再放心地重构代码以改善程序的性能,而无须担心破坏应用程序的规格说明
  • 验收测试

验收测试的目的是判断应用程序是否满足如功能性和易用性等其他需求,在设计/计划阶段,由开发人员和客户来设计验收测试。

第九章 测试因特网应用系统

在设计和测试基于因特网的应用系统时,由于有太多无法控制的因素,相互依赖的组件数量也非常之多,因此我们将会面临许多挑战,例子如下:

  • 用户群庞大且五花八门:浏览器、操作系统、设备种类及连接速率都有不同。
  • 业务环境。
  • 地点。
  • 测试环境。
  • 安全性。

表示层的测试:主要目的是发现应用程序的GUI或前端中的错误。其三个主要内容包括:

  1. 内容测试:包括整体审美、字体、色彩、拼写、内容正确性和默认值。
  2. Web站点结构:包括无效的链接和图形。
  3. 用户环境:包括Web浏览器版本和操作系统配置。

业务层的测试:重点是发现因特网应用系统的业务逻辑中的错误。可以测试的特性包括:

  1. 性能:测试的目的在于检查应用系统是否满足书面的系统规格说明(通常定义为响应时间和吞吐率)。
  2. 数据有效性:测试的目的在于发现从客户那里采集到的数据中的错误。
  3. 事务:测试的目的在于发现事务处理过程中的错误,其中可能包括信用卡处理、电子邮件验证以及消费税计算等。

数据层的测试:主要是指对应用系统用于储存和获取信息的数据库管理系统的测试。应当在特定的方面查找错误,包括:

  1. 响应时间:应量化数据操作语言(DML,包括结构化查询语言SQL中的INSERT、UPDATE和DELETE)、查询(SELECT)及事务的完成时间。
  2. 数据完整性:验证数据存储适当且正确。
  3. 容错性和可恢复性:最大化MTBF,最小化MTTR。

词汇表

  • Black-box testing (黑盒测试)
  • Bottom-up testing (自底向上的测试):增量测试的一种形式。
  • Boundary-value analysis (边界值分析)
  • Branch coverage (分支覆盖)
  • Cause-effect graphing (因果图分析)
  • Code inspection (代码检查)
  • Condition coverage (条件覆盖)
  • Data-driven testing (数据驱动测试)
  • Decision/condition coverage (判定/条件覆盖)
  • Decision coverage (判定覆盖)
  • Desk checking (桌面检查):一种将代码审查和走查技术结合起来,在用户桌面上执行程序的技术。
  • Equivalenie partitioning (等价类划分)
  • Exhaustive input testing (穷举输入测试)
  • External specification (外部规格说明)
  • Facility testing (能力测试):系统测试的一种类型,判断目标文档提及的每一项能力(或功能)是否都实现了。不要混淆能力测试与功能测试。
  • Function testing (功能测试)发现程序与其外部规格说明之间存在不一致的过程。
  • Incremental testing (增量测试):模块测试的一种形式,将待测模块与已测模块组装在一起进行测试。
  • Input/output testing (输入/输出测试)
  • JVM:Java Virtual Machine (Java虚拟机)的缩写
  • LDAP:Lightweight Directory Application Protocol(轻量目录应用协议)的缩写。
  • Logica-driven testing (逻辑驱动测试)
  • Multiple-condition coverage (多重条件覆盖)
  • Nonincremental testing (非增量测试):模块测试的一种形式,每个模块单独进行测试。
  • Performance testing (性能测试)
  • Random-input testing (随机输入测试)
  • Security testing (安全性测试)
  • Stress testing (强度测试)
  • System testing (系统测试)
  • Testing (测试):为了发现错误而执行程序(或具体的程序单元)的过程。
  • Top-down testing (自顶向下测试):增量测试的一种形式。
  • Usability testing (易用性测试)
  • Volume testing (容量测试)
  • Walkthrough (走查)
  • White-box testing (白盒测试)

About Kaveri, Yi XU

Agile Coach & Consutlant
This entry was posted in Agile&Scrum&Testing&TA. Bookmark the permalink.

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s