Lyft 微服务研发效能提升实践 | 4. 基于自动验收测试的部署门禁

简介: Lyft 微服务研发效能提升实践 | 4. 基于自动验收测试的部署门禁

怎样才能提高研发效率?是依赖于各自独立的本地开发测试环境,还是依赖完整的端到端测试?Lyft 的这一系列文章介绍了其开发环境的历史和发展,帮助我们思考如何打造一套适合大规模微服务的高效研发环境。本系列共 4 篇文章,这是第 4 篇。原文:Scaling productivity on microservices at Lyft (Part 4): Gating Deploys with Automated Acceptance Tests[1]


image.png


本文是本系列文章的第四篇,也是最后一篇,主要讲述我们在 Lyft 面对越来越多的开发人员和服务时,如何扩展开发实践。



之前的文章中,我们描述了如何利用上下文传播从而允许多个工程师在共享的预发环境中进行端到端测试。现在我们看看另一部分——自动端到端测试,我们将介绍如何构建一个可伸缩解决方案,让工程师在部署到生产环境之前更有信心。


重新思考端到端测试


本系列的第1部分介绍了在 CI 中运行集成测试时遇到的许多挑战。服务和工程师数量的爆炸式增长导致运行测试的远程开发环境(Onebox)难以扩展,运行测试需要耗费大量时间。每个服务的集成测试也变得非常笨拙,差不多需要花费一个多小时才能完成,并且信噪比极低。工程师们不信任失败的测试,经常将其忽略,否则就会浪费更多的调试时间,而这会让事情更糟糕。


在覆盖 900 多个服务的数千个集成测试中,有一小组真正有价值的端到端场景,我们认为维护这些场景至关重要。比如用户可以登录,请求乘车,支付车费。这些场景中的问题在内部称为 SEV0(严重程度最高的事件)。这些问题将使乘客无法到达需要去的地方,或者使司机无法获得收入,因此必须不惜一切代价解决问题。


验收测试


当我们着眼于想要维护好的具有最高价值的端到端测试时,即使粗略看一下,也会发现它们看起来很像验收测试。这些测试在不需要了解内部实现细节的情况下,描述了用户如何与 Lyft 平台进行的交互。


考虑到这一点,我们决定从分布式模型(每个服务定义自己的集成测试集)转移到小型集中式验收测试集。这样做有两个好处。在技术上,将场景放在一起可以帮助我们消除重复,并在相关服务之间共享测试代码。在组织上,由一个单一的所有者负责协调这些测试的整体健康状况(这些测试仍然由不同的人编写和修改),并设计更好的隔离,以避免失控。


另一个关键决策点是何时运行这些测试。我们希望改变将端到端测试作为“内部”开发循环的一部分运行(在第2部分中描述了)的习惯,之前开发人员习惯于在开发过程中多次执行端到端测试,以取代单元测试或调用特定服务端点等策略。相反,我们想让 CI 快速运行,并鼓励人们更习惯于将端到端测试推迟到过程的后期。由于这些原因,我们选择在部署到预发环境后运行验收测试,作为生产环境部署的门禁策略之一。


构建框架


引擎


首先,需要一个引擎来提供简单的界面,以便像真正的用户一样使用 Lyft 的 API。幸运的是,我们已经在预发和生产环境中构建了类似的东西用于生成流量(参见第1部分中的预发环境)。这个库由几个关键概念组成:


  • Actions(动作): 与 Lyft API 交互,例如,RequestRide Action 会调用 Lyft API,提供所需的出发地和目的地,开始寻找司机。
  • Behaviors(行为): 大脑会根据一定的概率决定下一步该做什么,例如,假设刚刚请求乘车,下一步应该取消还是继续等待司机?
  • Clients(客户端): 代表与平台交互的设备,通常是运行 Lyft 应用程序的手机,用于存储状态和协调 actions/behaviors。


这三个简单理念的结合是我们过去 5 年在预发和生产环境进行自动化测试的策略的基础,为我们提供了很好的服务。然而,要在验收测试中重用这些策略仍然需要考虑一个重要的差别——行为的概率性质(probabilistic nature)。在构建带有负载测试的行为时,考虑到随机性非常有助于消除意外 bug,因此我们将其设计为类似于模糊测试(fuzzer)[2]的东西,而这并不适合于具有确定性的对特定流的验收测试。


image.png


因此我们更新了库,允许客户端按照一系列步骤操作,作为行为的替代方案,从而弥补了这一差距。步骤可以是以下任何一个:


  • Actions(动作): 如上所述,只是执行一个 API 调用。
  • Conditions(条件): 阻塞下一步的执行,直到某个表达式为真,并有可选的超时时间,例如,司机可能会等到到达起点时,通过 PickedUp 动作通知 Lyft 已经接到了乘客。
  • Assertions(断言): 确保客户端状态看起来与预期相符,例如,我们希望确保在请求乘车之前完成报价。


定义测试


actions、conditions 和 assertions 构建块就位之后,接下来需要决定在新的集中式主系统中定义测试的格式。以前的集成测试是在代码中进行的,但是我们决定切换到使用自定义配置语法定义验收测试。尽管有利有弊,但我们发现以这种方式定义测试能够提供一种强制功能,以保持测试的简单性和一致性,从而让更多人能够读/写测试,以及更好的维护测试。配置中暴露有限的接口,将大多数逻辑实现到前面提到的库中,从而可以更好的与其他验收测试或负载测试运行程序共享。


把所有这些放在一起,看看下面的测试场景示例:


# test_scenarios/standard_ride.yaml
description: A standard Lyft ride between 1 driver and 1 passenger
clients:
  - role: passenger
    steps:
      - type: Action
        action: Login
      - type: Action
        action: SetDestination
      - type: Assertion
        assertions:
          - ["price_quote", "between", 10, 20]
      - type: Action
        action: RequestRide
      - type: Condition
        conditions:
          - ["ride_status", "equals", "completed"]
      - type: Action
        action: TipDriver
  - role: driver
    steps: 
      - type: Action
        action: Login
      - type: Action
        action: EnterDriverMode
      - type: Condition
        conditions:
          - ["ride_request", "exists", true]
      - type: Action
        action: AcceptRideRequest
      - type: Action
        action: PickUpPassenger
      - type: Condition
        conditions:
          - ["location", "equals", "destination"]
      - type: Action
        action: DropOffPassenger


值得注意的是,如果从零开始的话,那么基于现有的测试框架(如 Cucumber/Gherkin[3])可能会更好。而在我们的例子中,扩展现有的流量生成工具比尝试使用这些技术要容易得多。


部署门禁


我们将端到端测试从 PR 合并之前调整到合并后部署之前,很大程度上提高了开发人员的生产力。虽然一个典型的 PR 可能平均会包含 4 个提交(每个提交都会运行测试套件),但通常一次只会部署一到两个 PR,因此开发人员由于测试的不稳定而被阻塞的频率几乎减少了 10 倍。此外我们预期,如果 PR 没有了端到端测试提供的虚假的安全感,开发人员就会把更多资源投入到单元测试中,并且会建立 feature flag 等更安全的发布策略(我们在第3部分中讨论过现在可以基于每个请求进行配置覆盖)。


为了实现这一点,我们扩展了内部部署系统的门禁(deploy gate)概念。部署阶段可以被一个或多个门禁所阻塞,门禁表示允许部署进入下一个阶段之前必须满足的条件。一个典型的门禁例子就是我们在每个预发部署中都会包含的 bake time,这个门禁确保部署的系统运行了特定长度的时间,以便有任何问题的时候,可以给持续的模拟流量一个触发告警的机会。


每个验收测试都会将门禁添加为被测服务的依赖项,一旦预发环境部署完成,相应的门禁就会启动测试运行,并报告成功或失败。为了不至于减缓开发人员的速度,验收测试的目标是在比默认 bake time(10 分钟)更短的时间内完成。


image.png


实践


测试什么?


可以说,转换到验收测试的最困难的部分之一是确定验收测试的构成规则,并在大量集成测试中应用这些规则。在筛选了数百个集成测试并与服务所有者讨论之后,我们确定了以下标准:


  • 验收测试应该只代表关键业务流,应该从用户角度描述与 Lyft 平台的端到端交互。
  • 我们显然不想测试所有场景,因此验收测试对业务必须是关键的。作为套件中最昂贵的测试,我们无法负担测试那些短时间中断不会对业务造成重大损害(即 SEV0)的边缘情况或业务流。


虽然站在测试金字塔[4]的角度来看这些标准似乎很明显,但仍然比预期更难应用。开发人员对于删除集成测试的后果感到不安,无论该测试是否被很好的理解或者是否曾经捕获过 bug。为了简化转换,我们与团队合作,根据上述标准分析每一个测试。大约 95%的测试要么是多余的,要么可以重写为带有 mock 的单元测试。剩下的几个测试在去除冗余后被组合成大约 40 个总的验收测试场景,这些场景将取代所有的集成测试。


结果


从我们用预发环境验收测试取代 CI 中的集成测试以来,已经过去了大约 6 个月。场景数量保持相对稳定,我们已经将覆盖范围扩大到运输和自行车 &踏板车产品,每周进行几千次测试。我们看到的主要好处是:


  • 大多数 PR 都能在 10 分钟内通过单元测试并准备好合并(之前包含端到端测试时需要 30 分钟)。
  • 从服务中删除了数千个集成测试,无需花费大量时间来维护和调试这些测试。
  • 验收测试迭代起来更快、更可靠,只需要不到一分钟的时间就可以准备一个以预发为目标的本地环境(使用前面提到的本地开发工作流,而 Onebox 的初始设置时间为 1 小时)。
  • 自从将端到端测试从 PR 中移除后,泄漏到生产阶段的 bug 数量并没有明显增加。
  • 验收测试每周在问题泄漏到生产环境之前将其捕获。
  • 我们还没有看到希望的那样,在单元测试方面有额外的投资。这需要进一步的调查来理解为什么,以及我们是否可以/应该做的更多来改变这一点。


将来的工作


到目前为止,我们对从这些变化中看到的生产力提升感到兴奋,但仍然展望未来的许多改进。


预发隔离


目前,在将更改部署到预发环境后立即运行测试,可能会在出现问题时干扰到其他预发环境用户。我们希望在本系列第三篇文章中讨论的预发覆盖工作的基础上,在将新版本的服务公开给其他用户之前对其运行验收测试。这将为部署增加额外的延迟,因此需要评估收益是否大于成本。


测试覆盖率


考虑到测试背后的主要目标是提高可靠性,我们希望做更多更直接的改进,而不仅仅是维护这些测试。我们知道,今天的测试存在差距,这些差距是由之前的集成测试构建的,并与服务所有者讨论了哪些业务流重要,需要被测试覆盖。为了缩小差距并提高可靠性,需要确保真正的 iOS 和 Android 客户端所做的所有最常用的 API 调用都能在这些测试中得到体现。一个想法是对流经我们系统的真实流量和模拟流量之间的增量进行更多的分析,也许可以通过对分布式跟踪工具的进一步投资实现。


测试场景健康度


最初,平台团队手工策划了每个验收测试,并密切关注其稳定性。随着我们继续扩展更多的业务线,希望每个操作(API 调用)具有更细粒度的可观察性,这样就可以自动将故障发送给合适的团队处理。这并不意味着分散所有权(我们认为为更广泛的测试和平台保留中央所有者非常重要),只是更快的提醒产品团队他们的服务出现了故障,并尽量减少手工工作。


总结


在本系列文章中,我们仔细分析了 Lyft 多年来是如何发展开发和测试方法,并追求不断提升开发人员的生产力。我们介绍了 Lyft 开发环境的历史(第1部分),转向本地优先的第一次研发环境转型(第2部分),在预发环境隔离测试服务与 envoy 覆盖(第3部分),用部署期间用一组验收测试取代 PR 触发的较重的集成测试(本文)。


image.png


尽管这种方法可能没办法适用于所有环境,但在缩短开发人员的反馈循环方面取得了很大的成功,并极大简化了支持测试环境的基础设施,从而帮助开发人员持续输出代码。


References:

[1] Scaling productivity on microservices at Lyft (Part 4): Gating Deploys with Automated Acceptance Tests: https://eng.lyft.com/scaling-productivity-on-microservices-at-lyft-part-4-gating-deploys-with-automated-acceptance-4417e0ebc274

[2] Fuzzing: https://en.wikipedia.org/wiki/Fuzzing

[3] Gherkin: https://cucumber.io/docs/gherkin/

[4] The Pratical Test Pyramid: https://martinfowler.com/articles/practical-test-pyramid.html

目录
相关文章
|
7月前
|
人工智能 自然语言处理 测试技术
从人工到AI驱动:天猫测试全流程自动化变革实践
天猫技术质量团队探索AI在测试全流程的落地应用,覆盖需求解析、用例生成、数据构造、执行验证等核心环节。通过AI+自然语言驱动,实现测试自动化、可溯化与可管理化,在用例生成、数据构造和执行校验中显著提效,推动测试体系从人工迈向AI全流程自动化,提升效率40%以上,用例覆盖超70%,并构建行业级知识资产沉淀平台。
从人工到AI驱动:天猫测试全流程自动化变革实践
|
8月前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
2252 10
|
7月前
|
数据采集 存储 人工智能
从0到1:天猫AI测试用例生成的实践与突破
本文系统阐述了天猫技术团队在AI赋能测试领域的深度实践与探索,讲述了智能测试用例生成的落地路径。
从0到1:天猫AI测试用例生成的实践与突破
|
8月前
|
Java 测试技术 API
自动化测试工具集成及实践
自动化测试用例的覆盖度及关键点最佳实践、自动化测试工具、集成方法、自动化脚本编写等(兼容多语言(Java、Python、Go、C++、C#等)、多框架(Spring、React、Vue等))
648 6
|
8月前
|
jenkins Java 持续交付
使用 Jenkins 和 Spring Cloud 自动化微服务部署
随着单体应用逐渐被微服务架构取代,企业对快速发布、可扩展性和高可用性的需求日益增长。Jenkins 作为领先的持续集成与部署工具,结合 Spring Cloud 提供的云原生解决方案,能够有效简化微服务的开发、测试与部署流程。本文介绍了如何通过 Jenkins 实现微服务的自动化构建与部署,并结合 Spring Cloud 的配置管理、服务发现等功能,打造高效、稳定的微服务交付流程。
942 0
使用 Jenkins 和 Spring Cloud 自动化微服务部署
|
8月前
|
人工智能 边缘计算 搜索推荐
AI产品测试学习路径全解析:从业务场景到代码实践
本文深入解析AI测试的核心技能与学习路径,涵盖业务理解、模型指标计算与性能测试三大阶段,助力掌握分类、推荐系统、计算机视觉等多场景测试方法,提升AI产品质量保障能力。
|
8月前
|
人工智能 自然语言处理 测试技术
AI测试平台的用例管理实践:写得清晰,管得高效,执行更智能
在测试过程中,用例分散、步骤模糊、回归测试效率低等问题常困扰团队。霍格沃兹测试开发学社推出的AI测试平台,打通“用例编写—集中管理—智能执行”全流程,提升测试效率与覆盖率。平台支持标准化用例编写、统一管理操作及智能执行,助力测试团队高效协作,释放更多精力优化测试策略。目前平台已开放内测,欢迎试用体验!
|
9月前
|
人工智能 资源调度 jenkins
精准化回归测试:大厂实践与技术落地解析
在高频迭代时代,全量回归测试成本高、效率低,常导致关键 bug 漏测。精准化测试通过代码变更影响分析,智能筛选高价值用例,显著提升测试效率与缺陷捕获率,实现降本增效。已被阿里、京东、腾讯等大厂成功落地,成为质量保障的新趋势。
|
9月前
|
搜索推荐 Devops 测试技术
避免无效回归!基于MCP协议的精准测试影响分析实践
本文揭示传统测试的"孤岛困境",提出MCP(Model Context Protocol)测试新范式,通过模型抽象业务、上下文感知环境和协议规范协作,实现从机械执行到智能测试的转变。剖析MCP如何颠覆测试流程,展示典型应用场景,并提供团队落地实践路径,助力测试工程师把握质量效率革命的新机遇。
|
10月前
|
存储 监控 Shell
SkyWalking微服务监控部署与优化全攻略
综上所述,虽然SkyWalking的初始部署流程相对复杂,但通过一步步的准备和配置,可以充分发挥其作为可观测平台的强大功能,实现对微服务架构的高效监控和治理。尽管未亲临,心已向往。将一件事做到极致,便是天分的展现。