第4章 测试策略的实现
4.1 引言
- 戴明14条之一就是:“停止依赖于大批量检查来保证质量的做法。改进过程,从一开始就将质量内嵌于产品之中。”[9YhQXz]测试是跨职能部门的活动,是整个团队的责任,应该从项目一开始就一直做测试
- 质量内嵌是指从多个层次(单元、组件和验收)上写自动化测试,并将其作为部署流水线的一部分来执行,即每次应用程序的代码、配置或环境以及运行时所需软件发生变化时,都要执行一次
- 质量内嵌还意味着,你要不断地改进自动化测试策略
- 这些测试不仅仅对系统进行功能测试。容量、安全性及其他非功能测试也应尽早建立,也应该为它们写自动化测试套件。这些自动化测试确保不符合需求的问题能尽早暴露,降低其修复成本
- 如果在项目开始时就遵从适当的纪律准则,这种理想国是完全可以实现的。假如项目已经进行了一段时间,然后你才想实现这样的理想国,就有点儿困难了
4.2 测试的分类
- Brian Marick提出了如图所示的测试象限,它被广泛地应用于对为了确保交付高质量应用软件而做的各种类型的测试的建模
4.2.1 业务导向且支持开发过程的测试
- 这一象限的测试通常称作功能测试或验收测试。验收测试确保用户故事的验收条件得到满足。在开发一个用户故事之前,就应该写好验收测试,采取完美的自动化形式。验收测试可以测试系统特性的方方面面,包括其功能(functionality)、容量(capacity)、易用性(usability)、安全性(security)、可变性(modifiability)和可用性(availability)等
- 时新的自动化功能测试工具,比如 Cucumber、JBehave、Concordion以及Twist,都旨在把测试脚本与实现分离,
- 等价划分分析(equivalence partitioning analysis)和边界值分析可以帮助你得到尽可能小的用例集合,并保证测试覆盖完整的需求。然而,即便如此,你也要凭直觉来挑选一些最为相关的用例
自动化验收测试
- 自动化验收测试有很多很有价值的特性
- 它加快了反馈速度
- 它减少了测试人员的工作负荷
- 它让测试人员集中精力做探索性测试和高价值的活动,
- 这些验收测试也是一组回归测试套件
- 就像行为驱动开发(BDD)所建议的那样,使用人类可读的测试以及测试套件名,我们就可以从这些测试中自动生成需求说明文档
- 一般我们将代码覆盖率高于80%的测试视为“全面的”测试,但测试质量也非常重要,单单使用覆盖率这一指标是不够的
- 作为对自动化验收测试覆盖率比较好的一种评估方法,假设要替换系统中的某一部分(比如持久层,使用另一种实现来替换它)。当你完成替换时,运行了自动化测试,并且测试全部通过了。你有多大自信心,认为系统可以正常运行呢?
4.2.2 技术导向且支持开发过程的测试
- 有三种测试属于这一分类:单元测试、组件测试和部署测试。单元测试用于单独测试一个特定的代码段。因此,单元测试常常依赖于用测试替身(test double)模拟系统其他部分
- 然而,为了获得高速度,也有一些代价,即可能会错过应用系统不同部分之间交互时产生的一些缺陷
- 组件测试用于测试更大的功能集合,因此可能会捕获这类问题
4.2.3 业务导向且评价项目的测试
- 这类手工测试可以验证我们实际交付给用户的应用软件是否符合其期望
- 一种非常重要的面向业务且评价项目的测试是演示。在每个迭代结束时敏捷开发团队都向用户演示其开发完成的新功能。在开发过程中,我们也应该尽可能频繁地向客户演示功能,以确保尽早发现对需求规范的错误理解或有问题的需求规范
4.2.4 技术导向且评价项目的测试
- 验收测试分为两类:功能测试和非功能测试。非功能测试是指除功能之外的系统其他方面的质量,比如容量、可用性、安全性等
- 很多项目并不把非功能需求放在与功能需求同等重要的地位来对待,而且可能会更糟糕,他们根本不去验证这些非功能需求。虽然用户很少花时间提前对容量和安全性做要求,但一旦他们的信用卡信息被盗,或者网站由于容量问题总是停止运行,他们就会非常生气
4.2.5 测试替身
- 自动化测试的一个关键是在运行时用一个模拟对象来代替系统中的一部分。这样,应用程序中被测试的那部分与系统其他部分之间的交互可以被严格地掌控,从而更容易确定应用程序中这一特定部分的行为。这样的模拟对象常常就是mock、stub和dummy等
- 术语“测试替身”(test double),并进一步区分了各种测试替身
- 哑对象(dummy object)是指那些被传递但不被真正使用的对象。通常这些哑对象只是用于添充参数列表
- 假对象(fake object)是可以真正使用的实现,但是通常会利用一些捷径,所以不适合在生产环境中使用。一个很好的例子是内存数据库
- 桩(stub)是在测试中为每个调用提供一个封装好的响应,它通常不会对测试之外的请求进行响应,只用于测试
- spy是一种可记录一些关于它们如何被调用的信息的桩。这种形式的桩可能是记录它发出去了多少个消息的一个电子邮件服务
- 模拟对象(mock)是一种在编程时就设定了它预期要接收的调用。如果收到了未预期的调用,它们会抛出异常,并且还会在验证时被检查是否收到了它们所预期的所有调用
4.3 现实中的情况与应对策略
4.3.1 新项目
- 在这种情况下,最重要的事情就是一开始就要写自动化验收测试。为了能做到这一点,你需要:
- 选择技术平台和测试工具
- 建立一个简单的自动化构建
- 制定遵守INVEST原则[即独立的(Independent)、可协商的(Negotiable)、有价值的(Valuable)、可估计的(Estimable)、小的(Small)且可测试的(Testable)]的用户故事[ddVMFH]及考虑其验收条件
- 然后就可以严格遵守下面的流程:
- 客户、分析师和测试人员定义验收条件
- 测试人员和开发人员一起基于验收条件实现验收测试的自动化
- 开发人员编码来满足验收条件
- 只要有自动化测试失败,无论是单元测试、组件测试还是验收测试,开发人员都应该把它定为高优先级并修复它
- 从一开始,测试人员就应该参与需求写作的过程,并确保在整个系统演进的过程中,他们都能为具有一致性的和可维护的自动化验收测试套件提供支持
4.3.2 项目进行中
- 引入自动化测试最好的方式是选择应用程序中那些最常见、最重要且高价值的用例为起点。这就需要与客户沟通,以便清楚地识别真正的业务价值是什么,然后使用测试来做回归,以防止功能被破坏
4.3.3 遗留系统
- 没有自动化测试的系统就是遗留系统。虽然对这个定义还有一些争议,但它的确有用而且简单
- 通常比较有效的策略是在测试结束后仔细地验证系统的状态。如果时间来得及,你可以再测试一下这个用户故事的Alternate Path。最后,你还可以写更多的验收测试来检查一些异常条件,或防御一些常见的失效模式(failure mode),或防止不良的副作用
- 切记,只写那些有价值的自动化测试就行。如果只是增加新功能,而不需要修改这个提供支撑的框架代码时,为这部分代码写全面的测试是没有什么价值的
4.3.4 集成测试
- 我们所说的“集成测试”是指那些确保系统的每个独立部分都能够正确作用于其依赖的那些服务的测试
- 黑盒测试是价值最高的测试,并要列出外部系统所有可能的响应,并为每个响应写测试。这个模拟的外部系统需要找到某种方式识别你的请求,并回发正确的响应,假如有个请求不能被外部系统所识别,则应该返回一个异常
4.4 流程
- 最好的解决方案就是在每个迭代开始时,召集所有的项目干系人开个会。假如没有做迭代式开发,那么就在某个用户故事开始开发的前一周召开这样的会议。让客户、分析人员、测试人员坐在一起,找到最高优先级的测试场景
- 另一种方法是为测试创立一种DSL(Domain-Specific Language,领域专属语言),并用这种DSL来书写验收条件。我们最起码要让客户当场找出最简单的验收测试场景,并覆盖这些场景的Happy Path
管理待修复缺陷列表
- 达到这一目标的一个方法是,无论什么时候,一旦发现缺陷就立即修复它
- 还一种处理缺陷的方法,那就是像对待功能特性一样来对待缺陷
- 我们可以把缺陷分为严重(critical)、阻塞(blocker)、中(medium)和低(low)四个级别
4.5 小结
- 在很多项目中,测试被认为是一个由一些专职人员负责的独立阶段。可是,只有当测试成为与软件交付相关的每个人的责任,并从项目一开始就被引入并持续进行时,才能产生高质量的软件
书
- 《Agile Testing》
- 《敏捷开发的艺术》
- 《xUnit Test Patterns》
- 《Release It!》
工具
- 自动化功能测试工具,比如 Cucumber、JBehave、Concordion以及Twist