微软:用单元测试让测试左移

2022-12-05 12:18:56 浏览数 (3)

编译:TesterHome 原文标题:Shift testing left with unit tests 来源:Microsoft

测试有助于确保代码按预期执行,但建立测试的时间和精力会占用其他任务的时间,如功能开发。在这种时间成本下,从测试中获取最大价值是很重要的。本文讨论了DevOps的测试原则,重点是单元测试的价值和左移的测试策略。

专门的测试人员曾经写过大多数测试,许多产品开发人员没有学会写单元测试。人们可能对单元测试策略是否有效持怀疑态度,对写得不好的单元测试有不好的体验,或者担心单元测试会取代功能测试。

为了实施DevOps测试策略,首先要务实,尽管你可以坚持对新代码或现有代码进行单元测试,这些代码可以被干净地重构,但对于一个遗留的代码库来说,允许一些依赖性可能是有意义的。如果产品代码的重要部分使用SQL,允许单元测试对SQL资源提供者采取依赖性,而不是对该层进行模拟,可能是一种短期的进步方法。

随着DevOps组织的成熟,领导层变得更容易改善流程。虽然改变可能会有一些阻力,但敏捷组织重视那些明显能带来红利的改变。

DevOps 测试分类

定义一个测试分类法是DevOps测试过程的一个重要方面。一个DevOps测试分类法按其依赖性和运行时间对单个测试进行分类。开发人员应该了解在不同场景下使用的正确测试类型,以及流程的不同部分需要哪些测试。大多数组织将测试分类为四个层次:

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

虽然所有的测试都希望在任何时候运行是理想的,但这是不可行的。团队可以在DevOps过程中选择运行每个测试的位置,并使用左移或右移策略,将不同的测试类型在过程中提前或推迟。

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

DevOps测试准则

为L0和L1单元测试设定严格的准则。这些测试需要非常快速和可靠。例如,组件中每个L0测试的平均执行时间应该小于60毫秒。一个汇编中每个L1测试的平均执行时间应小于400毫秒。这个级别的测试不应超过2秒。

微软的一个团队在不到6分钟的时间里并行运行了60,000多个单元测试。他们的目标是将这个时间减少到一分钟以内。该团队用下图这样的工具跟踪单元测试的执行时间,并对超过允许时间的测试提出错误。

功能测试准则

功能测试必须是独立的。L2测试的关键概念是隔离。适当的隔离测试可以在任何序列中可靠地运行,因为它们对运行的环境有完全的控制。在测试开始的时候,状态必须是已知的。如果一个测试创建了数据并把它留在数据库中,它可能会破坏另一个依赖不同数据库状态的测试的运行。

需要用户身份的传统测试可能会调用外部认证供应商来获得身份。这种做法引入了几个挑战。外部依赖可能是不可靠的或暂时不可用的,从而破坏测试。这种做法也违反了测试隔离原则,因为一个测试可能会改变一个身份的状态,如权限,导致其他测试出现意外的默认状态。考虑通过在测试框架内投资于身份支持来防止这些问题。

DevOps测试原则

为了帮助测试组合过渡到现代DevOps流程,阐明了质量愿景。在定义和实施DevOps测试策略时,团队应坚持以下测试原则。

向左移动以提前进行测试

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

长时间运行的测试也可能产生故障,调查起来很费时间。团队可以建立对失败的容忍度,特别是在冲刺的早期。这种宽容破坏了测试作为对代码库质量的洞察力的价值。长时间的、最后一分钟的测试也给冲刺结束的预期增加了不可预测性,因为必须支付未知数量的技术债务,以使代码可以交付。

将测试向左转移的目标是通过在管道中更早地执行测试任务,将质量推向上游。通过测试和流程改进的结合,左移既减少了测试运行的时间,也减少了周期中后期失败的影响。向左移动确保大部分测试在变更合并到主分支之前完成。

除了将某些测试责任向左转移以提高代码质量外,团队还可以将其他测试方面向右转移,或在DevOps周期的后期,以改善最终产品。

在尽可能低的层次上编写测试

编写更多的单元测试。倾向于具有最少的外部依赖性的测试,并专注于运行大多数测试作为构建的一部分。考虑一个并行的构建系统,可以在装配和相关测试下降后立即运行装配的单元测试。在这个层面上测试一个服务的每一个方面是不可行的,但原则是使用较轻的单元测试,如果它们能产生与较重的功能测试相同的结果。

以测试的可靠性为目标

一个不可靠的测试在组织上成本是非常高的。这样的测试直接违背了工程效率的目标,因为它使人很难有信心地进行修改。开发人员应该能够在任何地方进行修改,并迅速获得没有被破坏的信心。保持高标准的可靠性。不鼓励使用UI测试,因为它们往往是不可靠的。

编写可以在任何地方运行的功能测试

测试可能使用专门设计的集成点来实现测试。这种做法的一个原因是产品本身缺乏可测试性。不幸的是,像这样的测试往往依赖于内部知识,并使用从功能测试角度来看并不重要的实现细节。这些测试仅限于拥有运行测试所需的秘密和配置的环境,这通常排除了生产部署。功能测试应该只使用产品的公共API。

为可测试性设计产品

处于成熟的DevOps流程中的组织对在云周期中交付高质量产品的含义有一个完整的看法。将平衡强烈地转向单元测试而不是功能测试,要求团队做出支持可测试性的设计和实施选择。对于什么是可测试性的精心设计和良好实现的代码,有不同的想法,就像有不同的编码风格一样。原则是,为可测试性而设计必须成为设计和代码质量讨论的主要部分。

将测试代码视为产品代码

明确指出测试代码就是产品代码,这就表明测试代码的质量与产品代码的质量一样重要。团队应该以对待产品代码的方式来对待测试代码,并对测试和测试框架的设计和实施进行同样的关注。这项工作类似于把配置和基础设施当作代码来管理。为了完整起见,代码审查应该考虑测试代码,并使其与产品代码具有相同的质量标准。

使用共享的测试基础设施

降低使用测试基础设施来产生可信赖的质量信号的标准。将测试视为整个团队的共享服务。将单元测试代码与产品代码一起存储,并与产品一起构建。作为构建过程的一部分运行的测试也必须在开发工具下运行。如果测试能够在从本地开发到生产的各个环境中运行,那么它们就具有与产品代码相同的可靠性。

让代码所有者对测试负责

测试代码应该与产品代码一起驻扎在一个 repo 中。对于要在组件边界进行测试的代码,要把测试的责任推给编写组件代码的人。不要依赖其他人来测试组件。

案例研究:单元测试的左移

一个微软团队决定用现代的DevOps单元测试和左移过程来取代他们的传统测试套件。该团队在三周一次的冲刺中跟踪进展,如下图所示。该图涵盖了第78-120个冲刺,这意味着在126周内有42个冲刺,或大约两年半的努力。

团队在第78个冲刺阶段开始了27K个遗留测试,在S120阶段达到了零遗留测试。一套L0和L1单元测试取代了大部分旧的功能测试。新的L2测试取代了一些测试,许多旧的测试被删除。

在一个需要两年多时间才能完成的过程中,过程本身可以学到很多东西。总的来说,在两年内完全重做测试系统的努力是一项巨大的投资。并不是每个功能团队都在同一时间做这项工作。整个组织的许多团队在每个冲刺阶段都投入了时间,在一些冲刺阶段,这是团队的大部分工作。虽然很难衡量这一转变的成本,但对于团队的质量和性能目标来说,这是一个不可谈判的要求。

开始工作

一开始,团队把旧的功能测试,也就是TRA测试,放在一边。团队希望开发人员能够接受编写单元测试的想法,特别是对于新功能。重点是使编写L0和L1测试尽可能的容易。该团队需要首先开发这种能力,并建立势头。

前面的图表显示了单元测试的数量在早期开始增加,因为团队看到了编写单元测试的好处。单元测试更容易维护,运行更快,故障更少。在拉动请求流程中,很容易获得对运行所有单元测试的支持。

团队在第101次冲刺前没有集中精力编写新的二级测试。同时,从第78次冲刺到第101次冲刺,TRA测试数量从27000个下降到14000个。新的单元测试取代了一些TRA测试,但根据团队对其有用性的分析,许多测试被简单地删除。

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

越来越快

一旦团队有了一个非常快速和可靠的持续集成(CI)信号,它就成了产品质量的一个可信指标。下面的截图显示了正在运行的拉动请求和CI管道,以及经过各个阶段所需的时间。

从拉动请求到合并大约需要30分钟,这包括运行60,000个单元测试。从代码合并到CI构建大约是22分钟。来自CI的第一个质量信号,SelfTest,在大约一个小时后出现。然后,大部分的产品都被测试了所提出的修改。从合并到SelfHost的两个小时内,整个产品都被测试了,并且该变化已经准备好进入生产了。

使用衡量标准

团队跟踪一个像下面这个例子的记分卡。在高层次上,记分卡跟踪两种类型的指标。健康或债务,以及速度。

对于现场健康指标,团队跟踪检测的时间,缓解的时间,以及一个团队正在进行的修复项目的数量。修复项目是团队在现场回顾中确定的工作,以防止类似事件再次发生。记分卡还跟踪团队是否在合理的时间范围内完成了维修项目。

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

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

1 人点赞