万亿市值的帝国崛起-微软DevOps转型实践(二)

2019-08-02 11:32:25 浏览数 (1)

DevOps at Microsoft

在上周,我发布了微软DevOps转型深度案例解析系列的第一篇文章《万亿市值的帝国崛起-微软DevOps转型实践(一)》,介绍了微软Azure DevOps团队工程效率的重要基石 - Release Flow 分支策略,文章非常受欢迎,也引发了很多技术讨论。

在上篇文章中我们也谈到,这家重回巅峰的公司其软件研发效能提升迅猛:“每天运行5亿个测试,每月超过400万次构建,每天发布42,000次”。看起来是不是已经很成功,足够快啦?

实际上,每当你觉得做得足够好了,这里永远还会有进一步的提升空间。因为软件研发效能是一种永无止境的、对于效率和质量的卓越追求。在文章发布后,有朋友给我反馈,这个数据已经又被刷新啦!最新的统计数据如下:

感谢徐磊老师提供的最新数据

从图中可以看到,目前微软每天会更新超过50万条工作项(包括需求、任务、测试用例、缺陷等),每天发布超过78,000次。

挺震撼的,是不是?接下来,就让我们来一起继续探秘之旅,深入解析其成功背后的更多技术转型实践。

有效DevOps的七个习惯

微软Azure DevOps团队产品经理Sam Guckenheimer在2015年曾经进行过一次公开演讲,分享了作为一个SaaS服务的提供商,应当如何有效开展DevOps。总结起来主要有以下七个方面:

  • 管理价值流动
  • 管理技术债务
  • 团队自组织和对齐
  • 持续学习和实验
  • 度量和收集数据
  • 生产为先的思维模式
  • 管理基础设施作为弹性资源

今天,我们就从很接地气的“管理技术债务”开始,介绍微软的“测试左移”转型实践,以及如何通过测试持续验证质量、协同开发有效管理技术债务。

微软的测试左移实践

转型之前

在几年之前,微软已经确定了“云为先、移动为先”的转型战略,但是在项目实践过程中,仍然在采用云时代之前的测试策略。虽然尝试变得更快,试图优化自动化测试,但实际过程中可谓是举步维艰。

1. 测试花费时间太长了

当时有一种测试集合被称为“NAR”,含义是Nightly Automation Run,即夜间自动化测试,有时甚至会持续22小时。当时有这种测试集合的命名其实挺无奈的,这就相当于告诉工程师整晚都要运行测试。另外还有一个测试集合被称为“FAR”,含义是Full Automation Run,即全部测试自动化测试集合,有时需要花费两天才能执行完。自动化测试本身是好事,但是执行这么久就未必了。

2. 测试失败太频繁了

当时几乎很难得到一次没有大量测试失败的测试执行。筛选、分析测试失败的原因需要花费大量时间,以至于团队大多数情况下只能忽略失败,直到迭代的末尾。我们从未看到过测试执行一次就100%通过的情况。

于是,我们聚焦在提升那些P0级的测试(数十个集成类型的测试)的可靠性上。让这些测试持续保持通过也非常痛苦。团队很紧密地安排这些测试执行,如果失败,则提交一个P0级的Bug并推送给研发解决。花费大量代价后,我们也只能让这些测试达到70%的全部通过概率。测试失败的原因五花八门:基础设施问题、产品问题、各种稀奇古怪的原因…

所以,我们无法忍受在Master分支上的这种无法信任的低效质量反馈。工程师提交代码到Master上,然后12小时才能获取第一个质量反馈,这样实在太慢了。由于团队经常忽略测试失败,在要发布的时候才发现距离发布质量要求差距太大,有可能数周时间才能把质量提升用于发布。

新的测试模型

问题很明显,那么解决方案也就逐步清晰了。在2015年,团队制定了新的质量愿景。其中核心的部分就是把整个测试组合向源头移动。

上面这张图反馈了测试组合应该有的样子,L0、L1级的测试是单元测试,L2、L3级的测试是功能测试。目前现有大多数测试都是L3级,我们要实现测试左移,转变为L0->L1->L2->L3全面覆盖、逐层晋级的模型。这类似于业界经常说的”理想的测试金字塔”测试模型。

测试的六大原则

我们整理出来团队应该坚持的测试六大原则,其实很多都应该是不言自明的。

1. 测试需要尽量在最低级别编写

我们在测试组合中,更倾向于那些只有最少外部依赖的测试,我们希望大多数的测试能够作为构建的一部分被运行。分布式构建系统能够快速运行大量的单元测试集合。我们当然知道,这个级别的测试不能覆盖服务的所有方面,但是基于这条原则,我们就不应该选择使用功能测试(L2/L3级别)去覆盖那些原本使用单元测试(L0级别)就能获得同样质量反馈的内容。

2. 测试编写一次,到处运行(包括在生产环境)

在以前,我们测试组合中的大量测试都使用服务对象模型。这里有很多原因,包括生产环境可测试性的匮乏。以前这种模型是我们不提倡的,有以下原因:首先,服务对象模型的测试依赖很多内部知识,而很多执行细节与功能测试视角是不相关的;其次,这样的测试还需要绑定环境信息,比如密钥和配置信息等,这都让我们的功能测试远离生产环境部署。刚才的图中有一个词“TRA”,指的是Tests Run Anywhere,即测试框架需要支持测试不仅仅能在受控的实验室环境中运行,而是在所有环境中都可以。

3. 生产环境要设计成可测试的

在云时代,打破单元测试(L0)与功能测试(L2/L3)之间的平衡,需要我们对可测试性进行设计和实现。关于具体如何更好地设计和编码才能实现可测试性的资料很多,这也与代码风格有关。这条原则让我们把可测试性的设计作为团队讨论代码设计和质量时的头等事情对待

4. 测试代码就是生产代码,只有可靠的测试才能留存下来

我们要把测试代码像产品代码一样对待。这条原则让我们把测试代码的质量级别提升到与产品代码相同。我们必须在测试设计、执行以及测试框架建设过程中给予同样级别的关注,代码评审时也同样要关注测试代码

一个不可靠的测试会浪费整个组织的时间去维护,会影响整体的工程效率。我们希望工程师可以在进行任何变更后,快速得到反馈并获得对变更正确性的深度自信,并确认没有影响到其他部分。我们要把测试的可靠性维护在一个相当高的级别,我们不鼓励大量使用UI测试,因为其不属于很可靠的那一类

5. 测试基础设施是共享服务

我们必须降低使用测试基础设施获得可信质量反馈的难度。单元测试(L0)需要与产品代码同源管理并且与产品一同构建。测试是整个团队的共享服务,需要给予良好的支持。

6. 测试所有权遵照产品所有权

最后一条原则就是测试需要遵照产品的所有权。换句话说,测试与产品代码在一起,如果你有一个组件并在组件的边界进行测试,你就不能依赖别人测试你的组件。我们把测试的责任和义务推给编写组件的人

实现测试左移!(Shift-Left)

这是流水线中质量观点的体现。测试左移让我们把质量在上游内建进去。也就是说,在变更代码合入到Master之前,大多数的测试就已经执行完,并且给出质量反馈了。

实践的推广

相信在国内推广过单元测试的小伙伴都会遇到同样的问题:如何说服团队?在转型以前,团队中有专职的测试人员编写大多数测试,而单元测试并没有什么人来写。于是,有人直接对新的测试组合策略提出了怀疑,单元测试大战正式打响!

生产环境真的可以通过单元测试保证质量?我们曾经在这件事上受过伤,这次推行单元测试有什么不同?管理层认同这种策略么?我们就像观看拔河比赛一样,看到两股力量的较量。其实大家不仅仅是考虑方法的优劣,还有很大一部分是在观察管理层的态度和支持力度

这时,有一些工程师对新的方法充满信心并开始尝试。我们对单元测试的类型和实现方式进行了严密的讨论,分为经典单元测试模式和有一定依赖的变通模式,也就是说完全隔离的单元测试和有一定依赖的单元测试。

对于每一个问题,我们都采用很务实的方式让飞轮运转起来。比如,我们让那些可以重构的代码或新代码使用经典单元测试模式,而那些遗留系统的代码库采用带有一定依赖的单元测试模式。比如,如果遗留系统的产品代码对SQL有依赖,那么我们在一定程度上可以允许单元测试存在一定依赖,而不是马上全部替换为mock。

我们要坚持“做对的事情”,但是过程中允许根据不同场景进行一定程度的变通。

测试组合的分级方法

我们传统的测试分级方式是根据测试执行时间,比如上文提到的“NAR”或“FAR”。新的测试分级方式是根据测试层级的和外部依赖。四个测试级别的分级方法如下:

一. L0/L1级别 - 单元测试

  • L0:经典的单元测试。这些快速在内存中运行的单元测试没有外部依赖,不需要产品被部署和运行起来。
  • L1:变通的单元测试。与L1级别测试类似,但是对SQL或文件系统等外部环境存在一定的依赖。

二. L2/L3级别 - 功能测试

  • L2:功能测试。需要“可测试的”服务被部署,但允许一些依赖的服务通过桩进行模拟。使用mock可以让测试简化并避免脆弱性。一个例子是将鉴权mock掉,这样测试就可以避免认证及管理密钥。一些L2级别的测试包含很少的UI自动化测试。
  • L3:集成测试。端到端的功能测试,需要在生产环境运行,需要产品被完全部署。这些测试也可以称为“生产环境测试”,大多数是UI自动化测试。团队会严格控制这类级别测试的数量,虽然随着产品的发展测试会逐步增多,但与L0/L1级别测试来比就很少了。

测试组合的关键点

1. 单元测试:快速和可靠性

我们要求每个L0级测试的平均执行时间小于60毫秒;每个L1级测试的平均执行时间小于400毫秒,最大不能超过2秒。目前我们并行运行60,000个单元测试,总时间小于6分钟,而未来的目标是小于1分钟。我们追踪单元测试执行时间,并设置门限。

2. 功能测试:良好的独立性

L2级测试的关键点在于隔离,良好隔离的测试可以按任何顺序执行。你可以可靠地运行良好隔离的测试,因为它们拥有在运行环境上完整的可控性。必须确保测试开始于一个已知环境,如果一个测试生成了一些数据并任由这些数据散落在数据库中,则可能会影响其他依赖数据库的测试。

设置"北极星",量化度量驱动改进

没有度量,就无法改进。以下图表展示了跨越数十个迭代的测试转型进展。

谈了这么多年测试左移的概念,还是头一次看到这么实在去落地的!

可以看到从Sprint 78开始,那时大概有2.7万个老的功能测试(橘色部分),在Sprint 120这些测试都已经被L0/L1级的单元测试、以及部分L2级新的功能测试替换掉了。团队完美地完成任务!这张测试组合的统计图是不是跟前面所介绍的测试模型图高度匹配?

让我们看看这个艰巨任务是如何完成的。首先,我们逐步建立团队的认同感,并从新功能开始编写单元测试,以便让飞轮开始转动起来。从图中可以看出单元测试逐渐增多,团队开始发现了好处:更容易维护、更快地运行、更少的失败。然后,我们在Pull Request工作流中开始运行所有的单元测试

我们在Sprint 101开始建立L2级的测试。与此同时,把原有2.7万个老的功能测试缩减到1.4万个。有一些老的功能测试被单元测试替换,但是很多测试其实已经无用了,基于团队的经验可以直接删除。我们按照这种方式逐步把老的功能测试减少到最少。

最后,在过去两年中重建测试系统是一个巨大的投资。每个迭代,很多团队都会在测试上进行投入。我们没有确切计算整体投入,但是如果没有测试左移的转型,我们的业务也就无法完成战略转型。我们知道从长期来看,这些转型肯定是值得的!

让我们跑得更快

当我们已经建立了持续集成系统并且可以快速和可靠地运行,就可以信任CI系统得到的质量反馈。下图反映了PR和CI流水线跨越不同阶段的效率数据。

从Pull Request提出到代码合并耗时30分钟,我们会在Pull Request中运行60,000个单元测试。从代码合并到CI构建耗时22分钟。第一个CI的质量信号(SelfTest)大约是1小时,在这时大多数产品测试已经完成了。在接下来的2小时(合并到SelfHost),整个产品测试全部完成,变更就可以发布到生产环境了。

总结

又是一篇五千多字的长文,还是感谢你看到最后:) 总结起来,本文的关键点如下:

  • 测试的六大原则:
    • 测试需要尽量在最低级别编写
    • 测试编写一次,到处运行
    • 生产环境要设计成可测试的
    • 测试代码就是生产代码
    • 测试基础设施是共享服务
    • 测试所有权遵照产品所有权
  • 实现测试左移(Shift-Left)
  • 坚持"做对的事情",但允许一定程度的变通
  • 测试组合分级方法,L0->L3递次晋级
  • 测试组合的关键点:
    • 单元测试:快速和可靠性
    • 功能测试:良好的独立性
  • 设置"北极星",量化度量驱动改进

以上就是微软DevOps转型系列文章的第二篇,主要介绍”测试左移”的实践和经验。

剧透:除了“测试左移”,其实还有“测试右移”,你听说过么?且听我们下回分解!

DevOps成功转型需要实践、文化、技术、人员等多个层次的支撑。我们不空谈、更聚焦落地,只有全面、深度解析成功公司的案例才会有价值,也欢迎大家跟我们一起持续精进。

0 人点赞