跳转到内容

测试


书籍 1: Agile Testing: A Practical Guide for Testers and Agile Teams

Section titled “书籍 1: Agile Testing: A Practical Guide for Testers and Agile Teams”

作者: Lisa Crispin & Janet Gregory (2009)

测试不是开发完成后才进行的一个阶段——它是贯穿整个敏捷开发过程的持续性活动。整个团队,而不仅仅是指定的测试人员,共同承担质量责任。敏捷团队中的测试人员充当客户的代言人,帮助团队构建正确的产品,并正确地构建产品。

质量是每个人的责任,不仅仅是测试人员的。开发人员编写单元测试,产品负责人明确验收标准,测试人员指导团队的质量实践。测试人员的角色从”在最后发现缺陷的守门人”转变为”从一开始就帮助预防缺陷的质量教练”。测试人员与开发人员和业务相关方持续协作,而不是等待交接。

2. Agile Testing Quadrants(敏捷测试象限)

Section titled “2. Agile Testing Quadrants(敏捷测试象限)”

这是本书最标志性的框架,改编自 Brian Marick 的测试矩阵。象限沿两个轴对测试进行分类:(1) 是支持团队(指导开发)还是评估产品(审视产品),(2) 是面向业务的还是面向技术的。

  • 象限 1(面向技术,支持团队): 单元测试、组件测试、集成测试。由开发人员编写,通常自动化,持续运行。这些构成测试自动化的基础。
  • 象限 2(面向业务,支持团队): 功能测试、示例、Story 测试、原型、模拟。这些测试从业务角度定义期望行为。它们驱动开发(如 BDD/ATDD),通常是自动化的。
  • 象限 3(面向业务,评估产品): 探索性测试、场景测试、可用性测试、用户验收测试、Alpha/Beta 测试。这些主要是手动的、创造性的、由人驱动的。它们评估产品是否真正交付了价值。
  • 象限 4(面向技术,评估产品): 性能测试、负载测试、安全测试、各类”性”测试(可靠性、可扩展性等)。通常需要专业工具和专业知识。

这些象限不是顺序阶段——团队在它们之间灵活切换。该模型帮助团队思考哪些类型的测试可能被忽视了。

3. Test Automation Pyramid(测试自动化金字塔)

Section titled “3. Test Automation Pyramid(测试自动化金字塔)”

借鉴自 Mike Cohn,该金字塔展示了自动化测试的理想分布:

  • 底层(最大层):单元测试。 快速、廉价、数量众多。它们提供快速反馈,是编写和维护成本最低的测试。
  • 中间层:服务/API/集成测试。 通过 API 或服务层测试业务逻辑,绕过 UI。
  • 顶层(最小层):UI/端到端测试。 昂贵、缓慢、脆弱。应尽量减少——只用于关键用户流程。

反模式是”冰淇淋甜筒”——过多的 UI 测试,过少的单元测试——导致缓慢、脆弱、昂贵的测试套件。

测试应随每次代码变更运行。团队应始终维护”绿色构建”——通过的测试套件。当构建失败时,修复它是团队的首要任务。持续集成(CI)在敏捷中不是可选的,而是基础性的。没有自动化测试带来的快速、可靠反馈,敏捷速度只是一种幻觉。

测试活动贯穿每个迭代,而不是在最后才进行:

  • 迭代之前: 测试人员参与 Story 梳理和规划。他们帮助编写验收标准并识别测试场景。这种”编码前的测试”是关键的敏捷测试实践。
  • 迭代期间: 测试人员与开发人员结对,编写和运行自动化测试,对已完成的 Story 进行探索性测试,并提供快速反馈。
  • 迭代结束: 演示、回顾以及必要的回归测试。目标是让 Story 达到”完全完成”状态——经过充分测试,可以随时交付。

敏捷测试人员是具有测试思维的通才。他们:

  • 与开发人员和产品负责人紧密协作
  • 帮助将业务需求转化为可测试的示例
  • 倡导自动化,但也重视探索性测试
  • 能够适应模糊性和变化
  • 促进技术与业务相关方之间的沟通
  • 在团队中充当”客户的声音”

作者强调,测试人员必须放弃”质量警察”的身份,拥抱协作和教练的角色。

7. Acceptance Test-Driven Development (ATDD,验收测试驱动开发)

Section titled “7. Acceptance Test-Driven Development (ATDD,验收测试驱动开发)”

也称为 Specification by Example(实例化需求)。团队在开发开始之前编写验收测试(可执行的规格说明)。这些测试有三个目的:(1) 澄清需求,(2) 指导开发,(3) 作为回归测试。提到的工具包括 FIT、FitNesse、Cucumber 和 Robot Framework。

关键洞察是:围绕编写这些测试的讨论比测试本身更有价值。“Three Amigos”会议——开发人员、测试人员和产品负责人讨论一个 Story——是核心实践。

8. Exploratory Testing(探索性测试)

Section titled “8. Exploratory Testing(探索性测试)”

探索性测试是同时进行测试设计、执行和学习。它不是随意或无结构的——它是一种有技巧的、有纪律的实践,由章程和时间盒(会话)引导。探索性测试在象限 3(评估产品)中至关重要,因为自动化测试只验证你已经知道的东西。探索性测试发现你不知道的东西——意外行为、可用性问题和边界情况。

推荐使用 Session-based Test Management(SBTM,基于会话的测试管理)来组织探索性测试,包括章程、时间盒和汇报会议。

作者倡导分层自动化策略:

  • 自动化重复性回归测试
  • 为不同层级使用合适的工具(单元测试框架用于象限 1,BDD 工具用于象限 2,性能工具用于象限 4)
  • 将测试代码视为生产代码——重构、审查、维护它
  • 不要自动化一切——某些测试手动做更好(探索性测试、可用性测试)
  • 投资于可测试性:设计易于测试的应用程序

10. Context-Driven Testing(上下文驱动测试)

Section titled “10. Context-Driven Testing(上下文驱动测试)”

测试方法必须适应团队的上下文——没有放之四海而皆准的方案。一个构建 MVP 的初创公司与一家医疗器械公司有着不同的测试需求。敏捷测试象限提供的是思考框架,而非规定性的检查清单。

  • “测试不是一个阶段” ——作者反复对比瀑布式的”测试阶段”与敏捷的持续测试思维。
  • “测试象限”可视化 ——一个 2×2 矩阵,成为本书的标志性贡献。团队可以直接指着一个象限问:“我们这类测试做得够不够?”
  • “全团队”隐喻 ——质量就像工厂中的安全:不仅仅是安全检查员的工作,而是每个人的责任。
  • Lisa 的个人故事 来自她在不同公司敏捷团队工作的经历,作为贯穿全书的案例研究,将抽象原则落地为真实世界的经验。
  • FIT/FitNesse 用于业务可读表格的验收测试
  • Selenium/Watir 用于 UI 自动化
  • Session-based Test Management 用于探索性测试
  • 思维导图用于测试规划
  • 测试矩阵用于覆盖率分析
  • 持续集成服务器(Jenkins, CruiseControl)
  • “Three Amigos”会议模式
  • 将自动化视为所有测试的替代品。 自动化至关重要但并不充分。探索性测试和人的判断仍然不可或缺。
  • “冰淇淋甜筒”反模式。 过度投资 UI 层自动化导致缓慢、脆弱、昂贵的测试套件。
  • 测试人员孤立工作。 如果测试人员没有嵌入团队并每天协作,他们就会成为瓶颈。
  • 不投资于可测试性。 如果应用程序的设计不考虑可测试性,自动化就会变得痛苦且昂贵。
  • 将测试自动化代码视为二等公民。 未经维护、重构和审查的测试代码会迅速退化,成为负债而非资产。
  • 跳过讨论。 不经过协作讨论就编写验收测试,错过了主要的收益。
  • 不根据上下文调整实践。 不考虑团队实际情况就盲目应用测试框架,只会造成浪费。

书籍 2: More Agile Testing: Learning Journeys for the Whole Team

Section titled “书籍 2: More Agile Testing: Learning Journeys for the Whole Team”

作者: Lisa Crispin & Janet Gregory (2014)

这本续作扩展了原版 Agile Testing,探讨了随着组织规模扩大、采用 DevOps 和敏捷成熟度提升,测试实践如何演进。核心论点是:构建质量文化需要持续学习、适应以及全团队的参与——包括运维、管理层和更广泛的组织。测试是一种思维方式和一套实践,必须与团队一起成长。

作者重新审视并扩展了第一本书中的象限模型。他们承认象限是一种启发式方法,而非严格的分类法。新的考量包括:

  • 跨职能测试,跨越多个象限(例如,安全测试既面向技术又面向业务)
  • 持续交付管道中的测试 ——象限如何映射到部署阶段
  • 随着系统变得更加复杂,象限 3(探索性测试)的重要性不断增长。自动化测试捕获已知回归;人的探索发现未知问题。

当多个团队在共享产品上工作时,测试变得更加复杂。关键策略包括:

  • 共享测试环境和测试数据管理 ——跨团队协调环境是最困难的规模化挑战之一。
  • 组件测试 vs. 系统集成测试 ——每个团队测试自己的组件;跨团队集成测试需要明确的协调。
  • 实践社区 ——跨团队的测试人员组成社区,分享知识、工具和模式。
  • Contract Testing(契约测试) ——当团队拥有不同的服务时,契约测试验证服务间的接口保持兼容。
  • Feature Toggles(功能开关) ——允许未完成的功能在不暴露给用户的情况下部署,即使跨团队也能实现持续集成。

测试超越了”生产前”阶段,延伸到运维领域:

  • 在生产环境中测试 ——监控、日志和告警是测试的形式。观察系统在真实负载下的行为是象限 4 的活动。
  • Canary Release(金丝雀发布)和 Blue-Green Deployment(蓝绿部署) ——先向一部分用户部署并监控问题,这是一种测试策略。
  • Infrastructure as Code(基础设施即代码)测试 ——配置管理脚本(Puppet、Chef、Ansible)与应用代码一样需要测试。
  • 部署管道作为测试策略 ——管道的每个阶段运行不同类型的测试,从单元测试(快速、优先)到探索性测试和性能测试(较慢、靠后)。

质量文化不是强加的——而是培育出来的。关键要素:

  • 心理安全 ——团队成员必须在没有指责的情况下,安全地报告缺陷、提出顾虑和承认错误。
  • 从失败中学习 ——事后审查(无指责回顾)是改进系统的机会,而非追责。
  • 共享所有权 ——“这不是我的工作”与质量文化背道而驰。每个人——开发人员、测试人员、运维人员、产品负责人——都拥有质量的所有权。
  • 可见性 ——让质量指标可见:构建状态、测试覆盖率趋势、缺陷逃逸率、部署频率。
  • 庆祝质量胜利 ——当团队预防了缺陷时给予认可和庆祝,而不仅仅是发现缺陷时。

作者深入探讨自动化策略:

  • 测试自动化作为软件开发项目 ——对测试代码应用相同的工程实践(版本控制、代码审查、CI、重构)。
  • 合适的抽象层级 ——测试自动化框架应使用领域特定语言或 Page Object 模式,将测试与实现细节解耦。
  • 数据驱动和关键字驱动测试 ——分离测试数据和测试逻辑,使测试更易维护和复用。
  • Visual Testing(视觉测试) ——比较截图以检测意外 UI 变化的工具(随现代 Web 开发出现的象限 4 关注点)。
  • 测试环境管理 ——容器(Docker)和虚拟化简化了环境配置,减少了”在我机器上能跑”的问题。
  • 处理遗留测试套件 ——驯服大型、缓慢、不稳定测试套件的策略:修剪冗余测试、重构脆弱测试、隔离不稳定测试。

作者使用 Dreyfus 技能习得模型(新手、高级初学者、胜任者、精通者、专家)来描述测试人员的成长。关键成长领域:

  • 技术技能 ——脚本编写、SQL、API 测试、架构理解
  • 领域知识 ——对业务领域的深入理解能带来更好的测试
  • 协作技能 ——促进对话、指导开发人员进行测试、沟通风险
  • 系统思维 ——理解组件如何交互、故障如何级联、哪些风险是系统性的

在持续交付(CD)管道中,每次提交都是一个候选发布版本。测试的影响:

  • 左移(Shift Left) ——尽早进行测试。预防比检测成本更低。
  • 右移(Shift Right) ——也通过监控、A/B 测试和可观测性在生产环境中测试。
  • 管道是安全网 ——每个阶段(构建、单元测试、集成测试、验收测试、性能测试、探索性测试、部署)都增加信心。
  • 快速反馈循环 ——如果测试套件运行需要数小时,它就无法提供敏捷所需的反馈。为速度而优化。

作者扩展了象限 4 的测试:

  • 性能测试应尽早开始,不要留到最后。建立基线并跟踪趋势。
  • 安全测试是每个人的关注点。使用威胁建模、静态分析和渗透测试。
  • 无障碍测试确保产品可供残障人士使用。自动化工具有帮助,但手动审查也是必要的。
  • 可靠性和弹性测试 ——Chaos Engineering(混沌工程,故意注入故障)测试系统是否能优雅降级。
  • “学习之旅” ——本书围绕持续学习的隐喻展开。每章代表团队成熟过程中的一个阶段。
  • 嘉宾故事 ——书中包含来自世界各地从业者的大量客座贡献,提供多样化的真实世界视角。
  • “测试自动化债务”概念 ——维护不善的自动化就像金融债务:它会随时间累积利息(维护成本)。
  • “全团队”概念的扩展 ——在第一本书中,“全团队”指开发团队。在这里,它扩展到包括运维、安全和更广泛的组织。
  • Docker 和容器用于测试环境配置
  • Contract Testing 工具(Pact)
  • Visual Regression Testing(视觉回归测试)工具
  • 监控和告警作为测试(生产验证)
  • Chaos Engineering 实践
  • Feature Toggles 用于生产环境测试
  • BDD 工具(Cucumber, SpecFlow)
  • 思维导图用于探索性测试章程
  • 假设第一本书的实践永远足够。 随着组织规模扩大和成熟,实践必须演进。
  • 忽视文化维度。 仅靠工具和流程无法创造质量。文化——信任、共享所有权、学习——才是基础。
  • 在测试环境管理上投入不足。 不稳定的环境导致不稳定的测试,进而侵蚀对自动化的信任。
  • 将非功能性测试推迟到最后。 性能、安全和无障碍问题在后期修复成本很高。
  • 将生产环境测试视为鲁莽行为。 配合适当的监控和部署策略(金丝雀、蓝绿),生产环境测试既安全又有价值。
  • 未能修剪测试套件。 只积累测试而不整理,导致缓慢、不可靠的套件,团队不再信任它们。
  • 不让测试人员参与 DevOps。 测试人员为部署管道、监控和事件响应带来批判性视角。

书籍 3: xUnit Test Patterns: Refactoring Test Code

Section titled “书籍 3: xUnit Test Patterns: Refactoring Test Code”

作者: Gerard Meszaros (2007)

测试代码就是代码。它值得与生产代码同等的关注、设计和重构纪律。结构不良的测试会成为维护负担,抵消测试自动化的收益。本书提供了一套全面的模式语言,用于编写清晰、可维护、健壮的自动化测试——以及一套”测试坏味道”目录,指示测试何时需要重构。

Meszaros 确立了所有测试自动化应追求的五个关键目标:

  • 测试作为规格说明 ——测试记录系统的预期行为。它们作为活的、可执行的文档。
  • 缺陷驱避剂,而非仅仅是缺陷检测 ——先写测试(TDD)防止缺陷被引入,而非仅仅在事后捕获它们。
  • 缺陷定位 ——当测试失败时,应立即明显看出缺陷在哪里。这要求测试小而聚焦。
  • 测试作为安全网 ——测试让开发人员有信心重构生产代码,而不必担心破坏现有行为。
  • 完全自动化和自检 ——测试必须无需人工干预即可运行,并自行报告通过/失败状态。自动化测试中的手动验证步骤违背了初衷。
  • 先写测试(TDD) ——推荐 Test-Driven Development:Red(编写失败的测试)、Green(让它通过)、Refactor(清理代码)。
  • 最小化测试重叠 ——每个行为应只在一个地方测试。冗余测试增加维护成本,却不能成比例地增加信心。
  • 每个测试验证一个条件 ——验证多个条件的测试更难理解,失败时提供的缺陷定位精度也更低。
  • 保持测试独立 ——测试不能依赖其他测试的执行顺序。每个测试应自行设置状态并自行清理。相互依赖的测试是最隐蔽的测试坏味道之一。
  • 保持测试简单和可读 ——测试就是文档。如果测试难以阅读,它作为文档就失败了,很可能被放弃。
  • 测试行为,而非实现 ——测试应验证系统做了什么,而非怎么做的。将测试过度耦合到实现上会使它们变得脆弱。

3. Test Smells(测试坏味道/问题)

Section titled “3. Test Smells(测试坏味道/问题)”

本书编录了丰富的测试坏味道分类——表明测试代码存在问题的症状:

代码层面的坏味道:

  • Obscure Test(晦涩的测试) ——测试难以理解。你看不出它在测什么或为什么测。子类型包括 Mystery Guest(依赖外部资源,其内容在测试中不可见)、General Fixture(设置超出测试需要的内容)和 Irrelevant Information(包含与测试无关的设置细节)。
  • Conditional Test Logic(条件测试逻辑) ——测试包含 if/else 语句、循环或其他条件逻辑。测试应该是线性的和可预测的。
  • Hard-Coded Test Data(硬编码测试数据) ——散布在测试中的魔术数字和字符串。使测试难以理解和维护。
  • Test Code Duplication(测试代码重复) ——复制粘贴测试代码。违反 DRY(Don’t Repeat Yourself)原则,使变更成本高昂。
  • Test Logic in Production(生产代码中的测试逻辑) ——生产代码包含仅用于支持测试的逻辑(例如 if (testing))。这污染了生产代码,是一种设计坏味道。

行为层面的坏味道:

  • Fragile Test(脆弱测试) ——即使行为未变,重构生产代码后测试也会失败。通常由过度规格化(测试实现细节而非行为)引起。子类型:Interface Sensitivity、Behavior Sensitivity、Data Sensitivity、Context Sensitivity。
  • Erratic Test(不稳定测试) ——有时通过有时失败的测试。原因包括相互依赖的测试、共享资源、时序依赖和资源泄漏。不稳定测试摧毁对测试套件的信任。
  • Slow Test(慢速测试) ——运行时间过长的测试,使开发人员不愿频繁运行。通常由不必要的数据库访问、网络调用或过度设置引起。
  • Manual Intervention(手动干预) ——需要人工设置数据、启动服务或验证结果的测试。这些不是真正的自动化。

项目层面的坏味道:

  • Buggy Tests(有缺陷的测试) ——应该失败时通过或应该通过时失败的测试。通常比没有测试更糟糕,因为它们创造了虚假的信心。
  • Developers Not Writing Tests(开发人员不写测试) ——更深层问题的症状:测试太难写、太慢,或者团队不重视它们。
  • High Test Maintenance Cost(高测试维护成本) ——更改生产代码需要更改大量测试。这是脆弱、耦合测试的最终后果。

4. Test Fixture Patterns(测试夹具模式)

Section titled “4. Test Fixture Patterns(测试夹具模式)”

“夹具”是测试所需的前置条件状态。如何设置和清理夹具对测试质量有深远影响:

  • Fresh Fixture(新鲜夹具) ——每个测试从头创建自己的夹具。对测试独立性最安全,但可能较慢。

    • Inline Setup(内联设置) ——设置逻辑直接在测试方法中。适合设置简短且与理解测试相关的简单情况。
    • Delegated Setup / Creation Method(委托设置/创建方法) ——设置逻辑提取到辅助方法中。减少重复同时保持测试可读性。辅助方法名称传达意图。
    • Implicit Setup(隐式设置,setUp 方法) ——类中所有测试共享的通用设置,放在框架的 setUp/beforeEach 方法中。风险:General Fixture 坏味道——设置超出任何单个测试的需要。
  • Shared Fixture(共享夹具) ——多个测试共享相同的夹具,通常只设置一次并重复使用。更快但风险更大。

    • 通过 setUp 共享的夹具 ——实践中很常见,但如果测试修改共享状态,可能导致相互依赖的测试。
    • Immutable Shared Fixture(不可变共享夹具) ——如果共享夹具从未被修改,则可以安全共享。这是共享夹具的推荐方法。
    • Lazy Setup(延迟设置) ——夹具在首次访问时创建,然后重复使用。有用但可能模糊依赖关系。
  • 夹具清理策略:

    • Garbage-Collected Teardown(垃圾回收清理) ——让运行时回收内存中的夹具。最简单。
    • Inline Teardown(内联清理) ——在测试中显式清理。容易出错(如果测试在清理前失败了怎么办?)。
    • Implicit Teardown(隐式清理,tearDown 方法) ——框架保证的清理。更可靠。
    • Table Truncation Teardown(表截断清理) ——对于数据库夹具,在测试之间截断表。可靠但慢。
    • Transaction Rollback Teardown(事务回滚清理) ——在数据库事务中运行每个测试,然后回滚。快速且可靠。

5. Test Double Patterns(测试替身模式)

Section titled “5. Test Double Patterns(测试替身模式)”

测试替身是在测试期间代替真实依赖的对象。本书提供了精确的分类法(已成为行业标准词汇):

  • Dummy Object(虚拟对象) ——传入以满足参数列表但从未实际使用。例如:一个方法需要 logger,但测试不关心日志记录。
  • Test Stub(测试桩) ——为方法调用提供预定的响应。用于控制被测系统(SUT)的间接输入。Stub 回答的问题是:“给定依赖项的这个输入,SUT 是否正确行为?”
  • Test Spy(测试间谍) ——类似 Stub,但还记录如何被调用。测试后,你检查 Spy 以验证 SUT 是否与依赖项正确交互。Spy 捕获间接输出。
  • Mock Object(模拟对象) ——预编程了期望。Mock 知道它应该接收什么调用以及以什么顺序。它自动验证交互,并在期望未满足时使测试失败。Mock 结合了桩和验证。
  • Fake Object(伪造对象) ——依赖项的轻量级、可工作的实现。例如:用内存数据库代替真实数据库。Fake 有真实行为但走捷径(例如,不持久化到磁盘)。

关键区分:状态验证 vs. 行为验证。

  • Stub 和 Fake 支持状态验证——在测试后检查 SUT 的输出/状态。
  • Mock 和 Spy 支持行为验证——检查 SUT 如何与其依赖项交互。

本书警告不要过度使用 Mock,这可能导致脆弱测试,即使外部行为正确,内部交互变化时也会失败。

6. Result Verification Patterns(结果验证模式)

Section titled “6. Result Verification Patterns(结果验证模式)”
  • State Verification(状态验证) ——在执行 SUT 后对其返回值或最终状态进行断言。最简单也最常见的模式。
  • Behavior Verification(行为验证) ——断言 SUT 使用特定参数调用了其依赖项的特定方法。使用 Mock 或 Spy。更强大但耦合度更高。
  • Custom Assertion(自定义断言) ——将复杂的断言逻辑提取到可重用的、表达意图的断言方法中。减少测试重复并使失败信息更具诊断性。
  • Delta Assertion(增量断言) ——对状态的变化而非绝对状态进行断言。例如:断言计数增加了 1,而非等于 5。对预存数据更加健壮。
  • Guard Assertion(守卫断言) ——测试开始时的断言,验证前置条件。如果守卫失败,测试的主断言将毫无意义。
  • Unfinished Test Assertion(未完成测试断言) ——故意的失败(例如 fail("Not yet implemented")),作为尚未完全编写的测试的占位符。防止误报通过。

7. Test Organization Patterns(测试组织模式)

Section titled “7. Test Organization Patterns(测试组织模式)”
  • Testcase Class per Class(每类一测试类) ——每个生产类一个测试类。简单但可能导致庞大的测试类。
  • Testcase Class per Feature(每功能一测试类) ——每个功能/行为一个测试类。对具有多种行为的复杂类更好地组织。
  • Testcase Class per Fixture(每夹具一测试类) ——将共享相同夹具的测试分组到一个类中。setUp 方法创建共享夹具,每个测试执行不同的方面。保持测试聚焦。
  • Test Utility Method(测试工具方法) ——用于创建对象、进行断言等的共享辅助方法。减少跨测试类的重复。
  • Parameterized Test(参数化测试) ——一个测试方法使用多组数据运行。对于用不同输入测试相同行为非常高效。

8. The Four-Phase Test Pattern(四阶段测试模式)

Section titled “8. The Four-Phase Test Pattern(四阶段测试模式)”

每个结构良好的测试遵循四个阶段:

  1. Setup(设置) ——建立测试夹具(前置条件)。
  2. Exercise(执行) ——调用被测行为。
  3. Verify(验证) ——检查预期结果。
  4. Teardown(清理) ——清理(通常是隐式的)。

这种模式(也称为 Arrange-Act-Assert 或 Given-When-Then)为所有测试提供了一致的、可读的结构。

  • “测试坏味道”隐喻 ——借鉴自 Martin Fowler 的”代码坏味道”,测试坏味道是某些东西有问题的症状,不一定是根本原因。它们引导你进一步调查。
  • “Mystery Guest”隐喻 ——测试依赖于外部数据文件或数据库记录,其内容在测试本身中不可见。就像推理小说一样,你必须到别处才能理解发生了什么。
  • “General Fixture”隐喻 ——设置一个大型复杂的夹具,而大多数测试只使用其中一小部分。就像你只需要一把椅子却装修了整栋房子。
  • 丰富的代码示例,使用 Java 和 C# 具体演示每种模式和反模式。
  • xUnit 框架(JUnit, NUnit, TestNG)
  • Mock Object 框架(EasyMock, jMock, Mockito)
  • 四阶段测试结构(Setup-Exercise-Verify-Teardown)
  • Object Mother 模式(创建测试对象的工厂)
  • Test Data Builder 模式(构建测试对象的流式 API)
  • Transaction Rollback Teardown 用于数据库测试
  • 自定义断言方法用于领域特定验证
  • 用 Mock 过度规格化测试。 测试每个交互而非有意义的交互,导致维护成本高昂的脆弱测试,每次重构都会失败。
  • 共享可变夹具。 导致相互依赖的测试——最隐蔽的不稳定测试形式。
  • 晦涩的测试。 如果测试难以阅读,它就无法作为文档,也不会被维护。
  • 不将测试代码视为一等公民。 没有重构、设计和审查的测试代码退化得和生产代码一样快。
  • 复制粘贴创建测试。 测试中的重复与生产代码中的一样有害。将通用设置提取到 Creation Method 或 Test Utility Method 中。
  • “脆弱测试”陷阱。 与实现细节紧密耦合的测试需要在代码重构时不断更新。这是高测试维护成本的头号原因。
  • 忽视慢速测试。 开发人员不再运行慢速测试,意味着它们不再提供价值。无情地优化测试速度。

书籍 4: Lessons Learned in Software Testing: A Context-Driven Approach

Section titled “书籍 4: Lessons Learned in Software Testing: A Context-Driven Approach”

作者: Cem Kaner, James Bach, Bret Pettichord (2002)

软件测试中没有最佳实践——只有在特定上下文中的好实践。测试是一项需要批判性思维、适应能力和深度参与特定产品、团队和情境的技能型智力活动。本书结构化为 293 条经验教训,按主题章节组织,将数十年的集体测试经验提炼为简洁、可操作的智慧。

  • “测试是通过探索和实验来学习,从而评估产品的过程。” 测试不是确认软件能用,而是找到利益相关者做决策所需的重要信息。
  • 你是迷雾中的灯塔,而不是守门人。 测试人员的工作是照亮风险并提供信息,而不是阻止发布或批准质量。是否发布是由测试信息支持的业务决策。
  • 对一切进行批判性思考。 质疑需求、假设、测试计划和你自己的结论。保持怀疑。
  • 测试人员是侦探,不是流水线工人。 测试需要调查、推理和创造力——不仅仅是机械地执行脚本。
  • 测试是无限的。 你永远无法测试所有东西。测试是在有限的时间和资源下做出聪明的选择。

本书最独特的贡献之一。发现缺陷只是开始——你必须为其修复进行倡导:

  • 编写清晰、有说服力的缺陷报告。 一份优秀的缺陷报告回答:你做了什么?发生了什么?你期望什么?为什么重要?
  • 缺陷报告是销售文档。 你在向开发人员(和管理层)推销修复这个缺陷的重要性。据此撰写。
  • 包含影响。 不要只描述症状——解释业务影响。“结账按钮在 Safari 上不工作”比”按钮不渲染”更好。
  • 重现并简化。 将步骤减少到重现缺陷所需的最少步骤。去除无关的复杂性。
  • 不要夸大严重性。 虚假警报会摧毁你的信誉。准确评估严重性,这样当你标记关键问题时,人们才会听。
  • 预期你的缺陷会被拒绝。 不是每个缺陷都会被修复。优先级排序是合理的。倡导,但优雅地接受决定。

作者展示了丰富的测试设计启发式方法:

  • Equivalence Class Partitioning(等价类划分) ——将输入划分为同一类中所有值行为相同的类别。从每个类中测试一个值。
  • Boundary Value Analysis(边界值分析) ——缺陷集中在边界处(最小值、最大值、偏移一位)。在边界处及附近进行测试。
  • Decision Tables(决策表) ——对于具有多个条件的复杂业务逻辑,在表中列举所有组合。
  • State Transition Testing(状态转换测试) ——将系统建模为状态机,测试状态之间的转换,包括无效转换。
  • Pairwise Testing(配对测试/全对测试) ——当输入组合数量巨大时,使用组合技术以更少的测试覆盖所有参数值对。
  • Error Guessing(错误猜测) ——利用经验和直觉猜测缺陷可能隐藏在哪里。这是一种随实践提高的技能。
  • Risk-based Testing(基于风险的测试) ——根据失败的概率和影响来确定测试优先级。最彻底地测试风险最高的区域。
  • Scenario Testing(场景测试) ——设计模拟真实端到端用户场景的测试,而非仅仅孤立的功能检查。场景测试通常能发现集成和工作流缺陷。
  • Soap Opera Testing(肥皂剧测试) ——场景测试的变体,将用户一生的经历压缩到一个戏剧性的会话中。夸大数据量、复杂性和使用模式。

作者对自动化持务实态度——甚至是警示态度:

  • 自动化是软件开发。 如果你将其视为次要活动,你会得到次等结果。应用软件工程实践。
  • 自动化测试不能发现缺陷。 它们确认之前能工作的东西仍然能工作。它们是回归检测工具。新缺陷由人通过探索发现。
  • 最常见的自动化失败是维护。 团队构建自动化,然后负担不起维护。自动化腐化并被放弃。
  • 不要自动化一切。 自动化重复性的、充分理解的、稳定的内容。将创造性的、依赖判断的测试留给人。
  • 工具导致的盲区。 过度依赖工具会将测试焦点缩小到工具能检查的范围。你不再去寻找工具看不到的东西。
  • 录制回放工具是脆弱的。 它们捕获实现细节(坐标、元素 ID)而这些经常变化。优先使用 API 级别或可编程的自动化。
  • 自动化的目的是扩展人的测试能力,而非取代人类测试人员。 自动化解放人去做更有创造性的、探索性的工作。
  • 测试是一个技能型专业,不是入门级的过渡岗位。 测试人员需要深厚的技术知识、领域专业知识和批判性思维能力。
  • 职业成长需要持续学习。 广泛阅读(不仅仅是测试书籍),学会编程,理解业务领域,学习认知心理学(偏见、启发式方法)。
  • 测试人员必须是出色的沟通者。 测试人员的很多价值来自清晰地传达信息——在缺陷报告中、在会议中、在测试报告中。
  • 与开发人员建立良好关系。 对抗性的测试人员与开发人员关系是有害的。协作,而非竞争。你们是拥有不同视角的盟友。
  • 专精与通才并重。 优秀的测试人员在许多领域拥有广泛知识,在少数领域有深厚专长。“T 型”专业人才。
  • 测试计划是假设,不是合同。 测试计划是你对需要什么测试的当前最佳猜测。它应随着你对产品了解的加深而演进。
  • 不要通过计算测试用例数来衡量测试。 测试用例数量是一个无意义的指标。一百个浅层测试提供的价值不如十个深入、周到的测试。
  • 缺陷数量不是测试人员质量的衡量标准。 发现的缺陷数量取决于代码的缺陷程度,而非测试人员的技能。将缺陷数量作为绩效指标会创造扭曲的激励(提交琐碎缺陷、将一个缺陷拆分为多个)。
  • 测试进度不是线性的。 你不会在 60% 的时间内”完成” 60% 的测试。最重要的缺陷通常在测试的最后 20% 才被发现,那时探索性洞察加深了。
  • 沟通风险,而非状态。 利益相关者需要理解已测试了什么、未测试什么以及存在什么风险——而不仅仅是执行了多少测试用例。

7. Context-Driven Testing(上下文驱动测试)

Section titled “7. Context-Driven Testing(上下文驱动测试)”

本书的总体哲学。上下文驱动测试的七项基本原则:

  1. 任何实践的价值取决于其上下文。
  2. 在特定上下文中有好的实践,但没有最佳实践。
  3. 人,协同工作,是任何项目上下文中最重要的部分。
  4. 项目随时间以通常不可预测的方式展开。
  5. 产品是解决方案。如果问题没有被解决,产品就不起作用。
  6. 好的软件测试是一个具有挑战性的智力过程。
  7. 只有通过在整个项目中合作行使的判断力和技能,我们才能在正确的时间做正确的事情,有效地测试我们的产品。

这一哲学与那些假设一种测试方法论普遍适用的规定性、流程繁重的方法形成对比。

8. Testing and Oracles(测试与预言机)

Section titled “8. Testing and Oracles(测试与预言机)”

Oracle(预言机)是你判断测试通过或失败的机制。作者强调预言机通常是隐式的和不完美的:

  • 没有完美的预言机。 即使规格说明也包含错误和歧义。
  • 常见的预言机包括: 规格说明、先前版本、可比产品、利益相关者的声明、测试人员自身的判断、标准和统计模型。
  • 启发式预言机 ——检测问题的实用经验法则:与历史的一致性、与可比产品的一致性、与用户期望的一致性、与目的的一致性。
  • 认识到预言机是可能出错的是测试成熟度的标志。测试可以”通过”(对照有缺陷的预言机)即使软件是错误的,反之亦然。

9. The Impossibility of Complete Testing(完全测试的不可能性)

Section titled “9. The Impossibility of Complete Testing(完全测试的不可能性)”
  • 你无法测试所有东西。 可能的输入、状态、序列和配置的数量实际上是无限的。
  • 每个测试都是样本。 你的测试套件从无限的可能测试空间中采样。你的采样策略质量决定了你的测试质量。
  • 停止试图”完整”。 相反,注重策略性:测试最重要的东西、风险最高的区域和最常见的用户场景。
  • 测试覆盖率指标是有误导性的。 100% 代码覆盖率并不意味着软件得到了良好的测试。覆盖率衡量的是哪些代码被执行了,而非重要行为是否被验证。
  • “测试就像犯罪调查。” 你收集证据、形成假设、追踪线索。你永远不会拥有所有信息。
  • “迷雾中的灯塔” ——测试人员照亮存在的东西,帮助团队导航。你不控制船只;你提供可见性。
  • “缺陷报告是销售文档” ——也许是本书最令人难忘的建议。将缺陷报告重新定义为说服而非文档记录,改变了测试人员编写它们的方式。
  • “Soap Opera Testing(肥皂剧测试)” ——之所以这样命名,是因为像肥皂剧一样,它将戏剧性的人生事件压缩到一个简短、紧张的故事线中。将多年的使用压缩到一个会话中。
  • 经验教训的格式本身 ——每条”教训”都是独立的(通常一两段),使本书可以浏览和引用。这种结构反映了作者的信念:测试智慧作为启发式方法比全面理论更好传达。
  • 来自作者咨询和测试职业生涯的轶事用真实世界中成功或失败的情况来说明几乎每一条教训。
  • 启发式测试设计(等价类、边界值、配对、状态转换)
  • Session-based Test Management(基于会话的测试管理)
  • 缺陷分类法和严重性分级
  • 基于风险的测试优先级排序
  • 探索性测试章程
  • 以风险沟通为重点的测试报告框架
  • 启发式预言机用于评估结果
  • Soap Opera Testing 技术
  • HTSM (Heuristic Test Strategy Model,启发式测试策略模型) ——一个基于质量标准、项目环境、产品要素和测试技术来生成测试想法的框架
  • 盲目遵循流程。 任何不考虑上下文就应用的测试流程都会失败。“最佳实践”标签是思维终结者。
  • 将测试等同于测试执行。 测试执行只是测试的一小部分。设计、调查、分析和报告同样重要。
  • 过度依赖自动化。 自动化放大了人的能力,但无法取代人的判断、创造力和探索。
  • 衡量错误的东西。 测试用例数、通过率和覆盖率百分比都可以被操纵,都可能产生误导。关注信息价值。
  • 与开发人员的对抗关系。 如果测试变成”我们对他们”的对抗,沟通就会崩溃,质量就会受损。
  • 编写缺乏说服力的缺陷报告。 技术上准确但枯燥、优先级不当的缺陷报告容易被忽视。
  • 假设需求是正确和完整的。 需求通常是错误的、模糊的或缺失的。测试应该挑战需求,而不仅仅是验证它们。
  • 将测试视为入门级工作。 在测试人员技能和职业发展上投入不足导致糟糕的测试成果。
  • 混淆覆盖率与质量。 “我们测试了所有东西”几乎从来不是真的。“我们彻底测试了最重要的东西”是一个可实现且有意义的目标。

四本书中涌现出几个共同主题:

  1. 测试是一项技能型智力活动。 所有作者都拒绝测试是机械性或低技能的观念。它需要设计思维、技术知识和沟通技能。

  2. 自动化至关重要但并不充分。 自动化测试提供回归安全,但需要人的探索性测试来发现未知的未知。各书在平衡方法上趋于一致。

  3. 测试代码就是生产代码。 无论是通过模式(Meszaros)、敏捷实践(Crispin & Gregory)还是来之不易的教训(Kaner 等人)来倡导,所有作者都坚持测试代码需要工程纪律。

  4. 上下文很重要。 没有通用的测试方法论。正确的方法取决于产品、团队、时间线、风险和领域。

  5. 质量是团队的责任。 “全团队”方法(Crispin & Gregory)与上下文驱动测试者坚持的协作优于守门(Kaner 等人)一致,也与 Meszaros 强调的开发人员拥有测试设计一致。

  6. 沟通与技术同等重要。 缺陷倡导(Kaner 等人)、Three Amigos(Crispin & Gregory)和可读测试(Meszaros)都强调,测试的价值只有在其发现被有效沟通时才能实现。

  7. 警惕虚假指标。 测试用例数、覆盖率百分比和缺陷数量都是质量的不完美替代指标。应关注风险、信息和信心。