使用单元测试向左移动测试

测试有助于确保代码按预期执行,但编写测试所需的时间和精力会占用完成其他任务的时间,例如功能开发。 鉴于测试的费用,务必要尽可能提取其最大价值。 本文讨论 DevOps 测试原则,重点介绍单元测试和 Shift-left 测试策略的价值。

大多数测试以往由专门的测试人员编写,许多产品开发人员没有学会编写单元测试。 编写测试似乎太困难,并且感觉像工作负担太重。 对单元测试策略是否有效、编写不善的单元测试的不良体验,以及担心单元测试会取代功能测试持怀疑态度。

描述采用单元测试的参数的图形。

若要实施 DevOps 测试策略,请务实并专注于构建势头。 尽管可以坚持对新代码或可以完全重构的现有代码进行单元测试,但旧代码库允许某些依赖项可能有意义。 如果产品代码的重要部分使用 SQL,则允许单元测试依赖于 SQL 资源提供程序,而不是 模拟 该层可能是一种短期的进度方法。

随着 DevOps 组织的成熟,领导层可以更轻松地改进流程。 虽然可能会对变革产生一些阻力,但敏捷组织珍视带来显著收益的变化。 传达更快速的测试运行且故障更少的愿景应该很容易,因为这意味着可以有更多时间通过开发功能来创造新价值。

DevOps 测试分类

定义测试分类是 DevOps 测试过程的一个重要方面。 DevOps 测试分类按依赖项和运行时间对各个测试进行分类。 开发人员应了解在不同方案中使用的正确测试类型,以及测试过程的不同部分所需的测试类型。 大多数组织对四个级别的测试进行分类:

  • L0L1 测试是 单元测试,或依赖于受测试程序集中的代码的测试,而没有其他测试。 L0 是一个广泛类别的快速内存单元测试。
  • L2功能测试,可能需要程序集和其他依赖项,如 SQL 或文件系统。
  • L3 功能测试针对可测试的服务部署运行。 此测试类别需要服务部署,但可能会对关键服务依赖项使用 存根
  • L4 测试是针对生产运行的受限 集成测试 类。 L4 测试需要完整的产品部署。

虽然所有测试能一直运行是理想的,但在实际上是不可行的。 团队可以选择在 DevOps 流程中运行每个测试的位置,并使用「左移」或「右移」策略,将不同类型的测试移动到流程的前期或后期。

例如,期望开发人员在提交之前始终运行 L2 测试,如果 L3 测试运行失败,拉取请求将会自动失败,如果 L4 测试失败,部署可能会被阻止。 具体规则可能因组织而异,但通过为组织内所有团队执行期望可以使每个人都朝着相同的质量愿景目标前进。

单元测试指南

为 L0 和 L1 单元测试设置严格的准则。 这些测试需要非常快速且可靠。 例如,程序集中每个 L0 测试的平均执行时间应小于 60 毫秒。 程序集中每个 L1 测试的平均执行时间应小于 400 毫秒。 此级别的测试不应超过 2 秒。

一个Microsoft团队在不到六分钟内并行运行超过 60,000 个单元测试。 他们的目标是将这一时间缩短到不到一分钟。 团队使用工具,例如如下图表,跟踪单元测试的执行时间,针对超过时间限制的测试进行故障报告。

一张专注于测试执行时间的连续图表。

功能测试指南

功能测试必须独立。 L2 测试的关键概念是隔离。 正确隔离的测试可以在任何序列中可靠地运行,因为它们可以完全控制他们运行的环境。 在测试开始时,状态必须已知。 如果一个测试创建了数据并将其保留在数据库中,则可能会损坏依赖于其他数据库状态的另一个测试的运行。

需要用户标识的旧测试可能已调用外部身份验证提供程序来获取标识。 这种做法引入了一些挑战。 外部依赖项可能暂时不可靠或不可用,从而中断了测试。 这种做法还违反了测试隔离原则,因为测试可能会更改标识(如权限)的状态,从而导致其他测试出现意外的默认状态。 考虑通过在测试框架中投资标识支持来防止这些问题。

DevOps 测试原则

为了帮助将测试项目组合过渡到现代 DevOps 流程,阐明质量愿景。 在定义和实施 DevOps 测试策略时,Teams 应遵循以下测试原则。

展示质量愿景示例并列出测试原则的图表。

向左移动以提前测试

测试可能需要长时间才能完成。 随着项目的规模,测试数量和类型会大幅增长。 当测试套件增长到需要数小时或数天才能完成时,他们可以更远地推送,直到最后一刻运行。 在提交代码后不久,才会实现测试的代码质量优势。

长时间运行的测试也可能产生需要耗费大量时间来调查的故障。 团队可以培养对故障的容忍度,尤其是在冲刺早期。 这种容忍度削弱了测试作为代码库质量洞察工具的价值。 长时间运行的最后一分钟测试也增加了不可预测性,以结束冲刺预期,因为必须支付未知数量的技术债务才能使代码可交付。

向左移动测试的目标是通过在管道中早期执行测试任务,将质量移到上游。 通过测试和过程改进的组合,向左移动可减少测试运行所需的时间,以及周期后面的故障影响。 向左移动可确保在更改合并到主分支之前完成大多数测试。

显示向左移动测试的移动关系图。

除了将某些测试职责向前移动以提高代码质量之外,团队还可以在 DevOps 流程后期参与其他测试方面,以改进最终产品。 有关详细信息,请参阅 Shift right 以在生产环境中进行测试

以最低可能级别编写测试

编写更多单元测试。 支持具有最少的外部依赖项的测试,并专注于在生成过程中运行大多数测试。 请考虑一个并行构建系统,该系统可以在程序集和相关的测试完成后立即运行程序集的单元测试。 在此级别测试服务的各个方面是不可行的,但原则是,如果它们能够产生与更重的功能测试相同的结果,则使用较轻的单元测试。

旨在测试可靠性

维护不可靠的测试会对组织造成高昂的成本。 这种测试直接针对工程效率目标工作,使很难自信地进行更改。 开发人员应该能够在任何地方进行更改,并快速地确认没有出现问题。 保持高标准的可靠性。 劝阻使用 UI 测试,因为它们往往不可靠。

编写可在任意位置运行的功能测试

测试可能使用专为测试设计的集成点来进行测试。 这种做法的一个原因是产品本身缺乏可测试性。 遗憾的是,此类测试通常依赖于内部知识,并使用从功能测试的角度来看无关的实现详细信息。 这些测试仅限于具有运行测试所需的机密和配置的环境,这些测试通常不包括生产部署。 功能测试应仅使用产品的公共 API。

设计可测试性产品

成熟 DevOps 流程中的组织全面了解在云节奏上交付优质产品意味着什么。 大力转变平衡,倾向于单元测试而非功能测试,这需要团队在设计和实施中做出支持测试性的选择。 对于设计良好且实现良好的代码,可测试性有不同的想法,就像有不同的编码样式一样。 原则是,设计可测试性必须成为有关设计和代码质量的讨论的主要部分。

将测试代码视为代码示例

明确指出测试代码是代码示例,表明测试代码的质量对于交付与代码示例一样重要。 Teams 应以对待产品代码的方式对待测试代码,并将相同的重视程度应用于测试和测试框架的设计和实现。 此工作类似于将配置和基础架构作为代码来管理。 为了做到全面,代码评审应考虑到测试代码,并且对其质量要求应与产品代码相同。

使用共享测试基础结构

降低使用测试基础设施生成可信质量信号的门槛。 将测试视为整个团队的共享服务。 将单元测试代码与产品一起存储,并使用产品生成它。 作为生成过程的一部分运行的测试还必须在 Azure DevOps 等开发工具下运行。 如果测试可以在从本地开发到生产的每个环境中运行,它们的可靠性与代码示例相同。

使代码所有者负责测试

测试代码应位于存储库中的代码示例旁边。 若要在组件边界测试代码,请向编写组件代码的人员推送测试责任。 不要依赖其他人来测试组件。

案例研究:使用单元测试左移

Microsoft团队决定将其旧版测试套件替换为新式 DevOps 单元测试和左移测试过程。 团队跟踪了每三周的冲刺进度,如下图所示。 该图涵盖了第 78 至 120 次冲刺,这意味着在 126 周内进行了 42 次冲刺,或者相当于大约两年半的时间。

该团队在迭代 78 中开始了二万七千个旧测试,并在 S120 时达到了零个旧测试。 一组 L0 和 L1 单元测试取代了大多数旧的功能测试。 新的 L2 测试取代了一些测试,并删除了许多旧测试。

显示一段时间内的示例测试项目组合平衡的关系图。

在需要两年多时间才能完成的软件旅程中,从流程本身中学到很多东西。 总的来说,在两年内完全恢复测试系统的努力是一项巨大的投资。 并非每个功能团队都同时工作。 整个组织内的许多团队在每次迭代中都投入了时间,在某些迭代中,这几乎是团队的全部工作。 虽然很难衡量这次转变的成本,但这是团队质量和性能目标的不可谈判的要求。

入门指南

一开始,团队就离开了旧的功能测试,称为 TRA 测试。 团队希望开发人员了解编写单元测试的想法,尤其是针对新功能。 重点是为了使编写 L0 和 L1 测试的过程尽量简化。 团队需要先开发此功能,并建立势头。

上图显示单元测试数量早早开始增加,因为团队看到了编写单元测试的好处。 单元测试更易于维护、运行速度更快,并且失败次数更少。 可以轻松获得在拉取请求流中运行所有单元测试的支持。

团队直到冲刺 101 才专注于编写新的 L2 测试。 与此同时,TRA 测试计数从 Sprint 78 到 Sprint 101 期间,从 27,000 减少到 14,000。 新的单元测试取代了一些 TRA 测试,但许多测试只是根据团队分析其有用性而删除的。

TRA 测试在第110次冲刺中从2100跃升至3800,因为在源代码树中发现了更多测试,并被添加到图表中。 事实证明,测试一直在运行,却没有被正确跟踪。 这不是一场危机,但重要的是诚实和根据需要重新评估。

变得更快

团队一旦拥有一个非常快速且可靠的持续集成(CI)信号,它就成为产品质量的受信任指标。 以下屏幕截图展示了合并请求和 CI 管道的实际运作情况,以及历经各个阶段所需的时间。

显示拉取请求和滚动 CI 管道的示意图。

从拉取请求到合并大约需要 30 分钟,其中包括运行 60,000 个单元测试。 从代码合并到 CI 生成大约 22 分钟。 CI 的第一个质量信号,SelfTest,大约在一小时后。 然后,大多数产品都使用建议的更改进行测试。 在从 Merge 到 SelfHost 的两小时内,将对整个产品进行测试,更改已准备好投入生产。

使用指标

团队跟踪一个评分卡片,如以下示例所示。 概括而言,记分卡跟踪两种类型的指标:健康状况与债务,以及速度。

显示用于跟踪测试性能的指标图表。

对于实时站点运行状况指标,团队跟踪检测时间、缓解时间以及团队携带的修复项目数。 修复项目是团队在实时站点回顾中识别的工作,以防止类似事件重复发生。 记分卡还跟踪团队是否在合理的时间范围内关闭维修项目。

对于工程健康指标,团队会跟踪每个开发人员的活跃缺陷。 如果团队每个开发人员有五个以上的 bug,团队必须在新功能开发之前优先修复这些 bug。 该团队还跟踪特殊类别(如安全性)中的老化 bug。

工程速度指标测量持续集成和持续交付管道的不同部分的速度(CI/CD)。 总体目标是提高 DevOps 管道的速度:从想法开始,将代码引入生产,并从客户那里接收数据。

后续步骤