在目前,软件开发团队中的各个成员(尤其是管理层)对软件测试工作的定义存在偏差。所以在正式介绍自动化测试的相关概念之前,读者需要花些时间来了解一下软件测试究竟是一项怎样的工作,即这项工作所要达成的目标、需要做的事情和要遵守的原则。
软件测试这项工作虽然属于计算机科学领域,但它同时需要考虑一些与经济学和心理学相关的因素。例如,在理想状态下,软件的开发商会希望通过软件测试工作能了解软件的所有情况,并证明它是一个“没有错误”的产品,但这个要求在实际生产环境中是无法满足的。首先,从科学的角度来说,大概率是不存在“没有错误”的软件的。其次,即使真的存在“没有错误”的软件,如果软件的测试人员想要证明“没有错误”,也需要在测试中穷举出该软件可能遇到的所有情况,并证明软件对每一种情况的应对都符合设计人员对它的预期。这从经济学角度来讲显然是不现实的,因为即使是十分简单的程序(如某一排序算法)也可能存在成千上万种的执行路径与输入/输出,恐怕没有哪一个软件的开发商能支持这种规模的软件测试工作。所以,如果读者想要真正地做好软件测试这项工作,首先要定义好工作的内容和要达成的目标,解决其中存在的主观愿望与客观成本之间的冲突问题。
而想要正确地定义软件测试的工作内容,读者首先需要对它有正确的理解。如果测试人员一开始就将这项工作定义为“证明软件的运行符合预期的过程”或者“向客户证明软件可靠的过程”,就会出现不符合科学现实的情况,就像前面说的,最终会走向证明软件“没有错误”的道路,成了不可能完成的任务。造成这种主客观冲突的主要根源在于,我们在心理上更倾向于执行具有建设性的任务,如程序员在开发软件时很自然地希望得到项目经理和客户的认可,所以行为上会下意识地去制造软件可靠且符合预期的表象。但软件测试的真正目标在于发现软件中还没有被发现的错误,或者所有人都没有预期到的情况,以便开发人员能继续提升软件的质量。换言之,软件测试本质上是破坏性任务,测试人员要做的实际上是类似于“鸡蛋里挑骨头”的工作。
想要让人们克服心理障碍,转而干“挑刺”工作,首先需要明确目标。具体来说,测试人员不能认为“没有发现错误”的测试是成功的测试,软件测试工作的目标是尽可能地找到软件中可以重现给其开发人员的错误。这就好像病人到医院看病,如果做了大量检查之后什么问题都没有找到,这显然不是一次成功的检查,毕竟只有找到病因,医生才能对症下药。只有怀揣这样的目标,测试人员才能确保自己不会在无意中做一个“老好人”,而会从一开始就铁了心做一个千方百计“刁难”待测软件的“坏人”。
综上所述,软件测试应该被定义成“为了发现错误而执行程序”的工作。
如果读者想要对软件测试这项工作建立全面的认知,除了理解软件测试工作的定义之外,还要了解在这项工作中可以执行哪些类型的测试。基本上,人们可以基于软件测试工作的各种不同目标和需求,在软件测试工作中执行如下测试。
● 单元测试。 单元测试通常是指对软件中的最小可测试单元进行检查和验证。至于“单元”的大小或范围,并没有明确的标准,“单元”可以是函数、方法、类、功能模块或者子系统。值得一提的是,人们往往将单元测试和白盒测试联系到一起,虽然从概念上来说,两者是有区别的,但单元测试和白盒测试通常都可被看作针对代码逻辑的测试,所以在某些语境下也可以认为这两者相同。
● 性能测试。 性能测试是指通过设计一些特定的测试用例来模拟多种正常、峰值和异常负载条件等,从而对系统的各项性能进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。负载测试可用于确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时系统各项性能的变化情况。压力测试是通过确定系统的瓶颈或者不能接受的性能点来测试系统能提供的最大服务级别的。
● UI测试。 在UI测试中,测试人员会通过设计一些特定的测试用例模拟用户在应用程序界面[如CUI(Command User Interface,命令行界面)、GUI(Graphical User Interface,图形用户界面)或Web界面]上进行的单击/双击、键盘输入等交互操作。在该测试过程中,这些测试用例会将模拟操作得到的反馈与一般情况下的人机交互反馈进行对比,以便找出UI的设计缺陷。
● 接口测试。 接口测试是最常见的软件测试之一,它通常需要测试人员能够排除GUI的影响,并针对软件的功能进行测试。它是软件业务逻辑测试中非常关键的任务。通常在软件项目的早期,接口测试就会同步进行,以便随时找出代码中存在的各种错误。另外,由于接口测试不使用GUI,因此它主要通过字符界面与测试人员进行交互。
关于上述类型的测试工作的具体执行方式,本书第4章会详尽介绍。在这里,读者只需要先对测试工作的分类有概念性认识即可。
在了解了测试工作中所要做的事情及其要达成的目标之后,读者接下来要考虑的就是如何做好一个铁了心要“刁难”待测软件的“坏人”了。如果我们想在测试工作中扮演好这种“坏人”的角色,就需要在工作实践中建立起一些基本原则,按照《软件测试的艺术》 [1] 这本书中的建议,我们可以归纳出如下工作原则。
● 程序员或开发团队应该避免测试自己编写的软件。 这很好理解,既然软件测试工作的本质是对待测软件进行各种“刁难”,那么自然要对自己的作品进行回避,否则很难保证测试结果不受测试人员的主观心理影响。
● 在测试用例中必须提出对软件输出或执行结果的预期。 或许很多人不相信,这个再正常不过的工作原则恰恰是软件测试工作中最常见的疏忽之一。这其实是心理学问题,即如果测试人员没有就测试用例在待测软件上得到的执行结果做出明确且清晰的预期,根据“所见即所想”现象,某个似是而非的、实际上是错误的结果就可能会下意识地被解释成正确的结论。而纠正这种下意识行为的一种方法,就是事先精确预期软件的输出,鼓励人们对所有的输出进行仔细检查。
● 每个测试用例的执行结果都应该得到充分的检查。 这也是一个显而易见但很容易被忽视的原则。我们经常会看到,即便某个错误的“症状”在测试用例的输出结果中已经清晰可见,测试人员也没有注意到这个错误的存在。
● 测试过程中不仅要考虑有效的数据和可以预期的执行结果,还要考虑无效的数据和预计不到的执行结果。 测试人员在测试软件时通常有一个很自然的倾向,即将测试重点集中在有效和可以预期的输入情况上,而忽略无效和无法预料到的情况。然而,软件产品在使用过程中发现的许多问题往往是在它们以某些新的或未预料到的方式运行时发现的。因此,针对未预料到的和无效输入情况的测试用例通常比针对可以预期的和有效输入情况的测试用例更具有价值。
● 测试过程中不仅要观察软件是否做了它该做的事情,还要关注它是否做了它不该做的事情。 这个原则是上一个原则的必然结果,必须检查软件是否有其开发者不希望出现的操作。例如,如果某个公司的工资管理系统可以为公司的每个员工生成正确的工资单,我们就可以认为它做了该做的事情,但如果该系统为公司的非雇员也生成了相应的工资单,这就是一个不小的错误,测试人员的职责就是发现它做了这件不该做的事情。
● 测试人员使用的测试用例应该是可以重复执行的,测试用例的执行结果也应该是可以重现的。 这个原则也是非常重要且显而易见的,试想一下,如果测试人员精心设计的测试用例在他发现某个错误并提交报告之后就被丢弃了,那么一旦软件在完成修改之后需要重新测试,测试人员就必须重新设计这些测试用例。这不仅是对人力资源的巨大浪费,而且测试人员很难保证重新设计的测试用例能对上一次发现的已修改的错误进行确认。除此之外,软件的其他部分被修改之后,也可能导致已经被测试过的部分出现新的问题,因此需要对其进行回归测试,这也需要测试人员保留并重复执行使用过的测试用例。
● 在制订工作计划、开发测试用例时不能假设软件中不存在错误。 这是项目经理经常容易犯的一种错误,因为他们对测试工作有不符合科学规律的预期。也就是说,他们误以为测试是一个“证明程序能正确运行”的过程,但测试实际上是“为发现错误而执行程序”的过程。关于这两种认知的差异及其造成的影响,本书之前已经做了详细的说明,这里不再重复。
● 软件的某一部分中可能存在的实际错误数量通常与该部分中已经发现的错误数量成正比。 在各种类型的工程中,人们总会观察到错误同时出现的现象,软件工程也不例外,每个软件都免不了会出现几个出错频率很高的部分。尽管没有人能够对这种现象给出很好的解释,但这足以让测试人员积累某种工作经验。换言之,如果程序的某个部分远比其他部分更容易产生错误,那么测试人员就可以基于这种经验对这部分进行重点测试,以便让测试工作获得更高的成效。
● 明白软件测试是极富创造性的工作,它给程序员带来的挑战并不低于软件开发所带来的挑战。 也许很多人没有意识到一个事实,即测试大型软件所需要完成的创造性工作在很多情况下是超过该项软件开发工作的。想要充分地测试软件并确保所有错误都不存在几乎是不可能的。这意味着测试人员需要使用一系列特定的技术,并针对具体的软件设计出合理的测试用例集,而这显然需要测试人员完成大量的创造性工作。
除了上述基本原则之外,对于某些具体的测试工作来说,妥善利用自动化测试来提升测试的效率和质量是一个非常重要的工作原则,这让自动化测试成为测试人员必须学习的一项重要技能。这项技能如今在流行的敏捷开发、持续交付和持续集成等新型软件工程理念中有着至关重要的地位。下面我们继续了解实现自动化测试的意义,以及自动化测试的核心能力及其局限性。
[1] 《软件测试的艺术》( The Art of Software Testing )是软件测试领域的经典著作,书中对软件测试的任务类型、测试用例的设计方法、测试策略等主题都有精彩且具体的论述,其中的许多经典论断至今仍被广泛引用。特别值得一提的是,此书仅100多页,非常适合专业人员每年精读一次,每次都会有新的感悟。