揭秘微服务端到端测试:优点与挑战并存

文摘   2024-10-16 07:03   四川  

端到端测试被广泛作为复杂系统质量保障的关键策略,特别是在微服务体系架构中。端到端测试验证完整的业务流程确保缺陷逃逸到生产环境就识别出来吸引了许多公司在此方法上投入巨资。然而,随着被测系统变得越来越复杂,服务之间的相互依赖也变得越来越复杂,端到端测试开始显现出其局限性。

在这篇文章中,我们将讨论在微服务架构中完全依赖端到端测试存在的问题。我们会讨论测试不稳定性、高昂的测试维护成本和调试困难是如何影响开发团队的敏捷性和效率的。此外,我们将看到价值交付的放缓、捕获缺陷的低效率以及生产中持续存在的缺陷如何证明重新思考这一策略的必要性。

软件开发的核心目标

当我们在软件工程中讨论质量时,我们经常把理解限制在没有缺陷或符合技术规范。然而,这种观点忽视了使数字产品真正有价值的方面。质量不仅仅是技术上的正确性,它还包括软件有效解决实际问题的能力,提供积极的用户体验,并满足业务期望

“质量”一词来源于拉丁语“qualitas”,来源于“qualis”,意思是“类型”或“性质”。最初,它指的是定义事物本质的属性或特征。随着时间的推移,质量的概念已经演变,不仅代表产品的内在特性,而且还代表其创造价值和满足期望的能力

许多数字产品虽然具有不同的解决方案但却具有相同的技术目的——例如为用户解决特定的问题或满足业务需求——但每种产品的质量可能存在显著差异。数字产品的质量不仅取决于它实现最终目的的能力,还取决于它如何实现这一目标。不同的产品可以解决相同的问题,但它们提供给用户的体验、效率、可靠性和美学以及其他因素,可以将一种产品与另一种产品区分开来


对于一小部分软件工程师、业务分析师甚至产品经理来说,质量可能会被错误地视为敏捷性的阻碍。这是因为质量保证过程本质上是严格和详细的。它不仅包括测试和验证,还包括不断的修复、调试,有时还会返工。这个过程乍一看可能会延迟新功能的开发和交付。但这种观点忽略了一个关键点:质量不仅仅是通过测试来发现缺陷;它是关于确保交付的产品真正满足用户和企业的期望和需求。一个快速发布但未能实现承诺的系统并不能提供价值——而且在发布后修复这些缺陷的成本远远高于在开发过程中避免这些缺陷所花费的时间。

试想一辆车。当汽车制造商推出一款新车型时,汽车在进入市场之前要经过一系列严格的测试——碰撞测试、安全测试、不同条件下的性能测试。进行这些测试并不是为了推迟汽车的发布,而是为了确保一旦汽车到达消费者手中,它是安全的,可靠的,能够满足未来车主的期望。

然而,当制造商在发布前未能保证汽车产品质量时,后果可能会很严重。汽车在市场流通后发现的问题通常会导致召回——这是一个复杂的过程,包括定位所有受影响的车辆,进行维修或更换,并管理客户的不满。典型的召回步骤包括:

  1. 问题识别:检测影响车辆安全或功能的缺陷。
  2. 与业主沟通:告知业主问题和纠正问题所需的步骤。
  3. 维修执行:安排维修或更换,这可能涉及将汽车带到授权经销商或车间的物流。
  4. 成本管理:吸收维修成本,物流,并可能补偿消费者。
  5. 恢复信心:努力恢复受影响消费者的信心,这可能包括处理不满和对车辆可能有其他问题的担忧。

对于被召回的车主来说,这个过程可能会产生既沮丧又担忧。除了不得不把车送去修理的不便之外,还有人担心可能会出现其他问题,对品牌的信心可能会动摇。对于汽车制造商来说,召回不仅是一笔巨大的财务支出,也是对声誉的巨大打击。

类似地,在软件工程中,测试是确保最终产品可靠和有价值的重要一环。如果我们不能在发布前确保质量,其后果比如生产漏洞、安全漏洞和用户信心的丧失——相当于汽车召回的影响。

软件工程师和测试专家James Bach将软件测试定义为“质疑产品以评估其质量的过程”。换句话说,测试是我们用来挑战软件的工具,在不同的情况下测试它,以确保它能够应对现实世界的挑战。

当一个新功能被开发出来时,它必须经过自己的“碰撞测试”。我们不仅要在理想条件下测试它,还要在不利的情况下测试它——高用户负荷、网络故障、与其他系统的集成——以确保它足够健壮,能够承受这些挑战。这些测试不应被视为一种延迟,而应被视为对产品质量的投资,进而对业务成功的投资。

因此,软件工程测试的真正目的是确保每个交付的功能都准备好面对现实世界。它们的存在不是为了阻碍开发,而是为了确保当功能到达最终用户时,它将达到预期,并能够产生预期的价值。当我们以这种方式理解测试时,很明显,它们是敏捷性和产品成功的朋友,而不是敌人。

评审对产品质量的重要性

在编写任何一行代码之前,软件开发中有一个关键阶段:评审阶段。在这个阶段,业务专家、产品所有者和软件工程师聚集在一起,深入探索和理解需要解决的问题。明确定义解决方案目标,也是确定解决方案应该如何为业务和用户产生真正价值的环节。

这个阶段的一个很好的类比是建造一座建筑。想象一下,一组工程师正在建造一座摩天大楼。在铺设任何一块砖之前,都需要进行密集的规划和工程工作,以确保基础足够牢固,以支撑结构。如果基础设计不当或施工不当,无论上层建筑施工得多么好,整个建筑都将处于危险之中。类似地,在软件开发中,发现阶段就像这个基础:它清楚地定义了建筑(或软件)应该是什么以及它应该如何工作。

对问题和目标的清晰理解对最终产品的高质量至关重要。如果没有一个基于研究和证据的定义良好的需求的坚实基础,任何开发的解决方案都有被误导的风险,不能满足业务或用户的实际需求。因此,质量其实早在测试阶段之前就开始了;它植根于评审阶段的思考和分析质量。

在这个阶段,软件工程师扮演一个积极的角色,与业务专家和产品所有者密切合作是至关重要的。他们提供技术视角,帮助塑造所提出的解决方案的可行性,并确保需求是清晰的、现实的和可实现的。如果没有这种密切的合作,误解和不一致的期望的风险将显著增加,这可能会损害最终产品的质量。

当这个阶段进行得很好,每个人都深入理解问题和解决方案的目标时,软件开发就有了坚实的基础。这反过来又促进了产品的创造,不仅履行其功能,而且以一种为企业和用户增加真正和可持续价值的方式。

因此,在考虑敏捷测试或交付之前,重要的是要记住,质量始于良好的发现。就像汽车要经过彻底的测试以确保其安全性和性能一样,软件也必须从一开始就进行扎实的规划和验证,确保每个功能都能有效地为业务和最终用户增加价值。

端到端测试呢?

在评审阶段建立了良好的基础,软件开发可以以清晰的方向感和目的进行。但是,即使尽了一切努力来确保规划是可靠的,仍然需要验证所开发的解决方案是否真的实现了所提出的内容。这就是端到端测试或端到端测试发挥作用的地方。

端到端测试旨在验证系统的所有组件是否按照预期工作,确保业务流程从头到尾正常工作。在微服务系统中,这些测试对于识别只有当所有服务一起运行、模拟最终用户体验时才会出现的问题至关重要。

然而,端到端测试虽然至关重要,但也并非没有挑战。当实现不充分或过度时,它们可能成为软件开发和交付过程中的一个重大瓶颈。需要协调多个服务和系统可能导致测试缓慢、不稳定和难以维护。此外,在复杂的体系结构中,获取反馈所需的时间可能很长,这反过来会损害团队的敏捷性。

另一个关键点是,虽然端到端测试的目的是覆盖整个业务流程,但它可能无法准确地指出问题的原因所在。当端到端测试失败时,可能很难识别问题是在特定的服务、服务之间的通信中,还是在系统的其他部分。这可能会导致耗时和令人沮丧的调试周期,这通常会导致返工和交付过程的整体减速。

因此,尽管端到端测试在复杂系统的验证中扮演着重要的角色,但应该明智地使用它。它的实现应该与其他形式的测试(如单元和集成测试、契约测试)保持平衡,这些测试可以对特定的系统组件提供更快、更独立的反馈。有效地组合这些不同类型的测试可以在不影响敏捷性的情况下提供全面的覆盖。下面让我们更详细地讨论。

理解端到端测试的注意事项

测试是我们用来验证软件可观察行为的工具,也就是说,我们开发的解决方案是否真的实现了它的目标。这包括测试功能是否满足业务需求,是否提供令人满意的用户体验,以及它在技术上是否可靠。为了确保可观察到的行为是正确的,我们应该在测试中投入与在软件本身中投入的质量一样多的质量。这适用于所有类型的测试,无论是代码单元测试、验收测试、契约测试还是端到端测试。

每种测试策略都有其优缺点,必须以适当的剂量应用它们。例如,单元测试提供快速反馈并隔离特定组件中的问题,但不能捕获集成失败。验收测试验证软件是否满足业务需求,但可能不包括所有的技术细微差别。契约测试确保服务之间的通信是正确的,但不能保证系统的完整行为。端到端测试虽然是验证整个流程的关键,但如果使用不当,可能会成为陷阱。

我们希望避免落入这些陷阱,以免在向业务交付价值时损害敏捷性。因此,重要的是要明白,尽管端到端测试扮演着重要的角色,但它们不是最终的策略,应该谨慎使用。

在这种情况下,我们将重点关注盲目相信端到端测试是最终解决方案时可能面临的一些常见陷阱。我们将看到需要特别注意的八个关键点:

  1. 反馈的时间延长:随着端到端测试套件的增长,完成测试所需的时间也会增加。这意味着工程师需要等待更长的时间来收到关于他们所做更改的反馈,这可能会延迟开发并降低团队的效率。
  2. 对结果缺乏信心:不一致失败的测试,也被称为“Flaky Tests”,是一个巨大的挑战。它们可能对相同的代码给出不同的结果,这导致需要重新运行测试以确认是否存在问题。这导致对测试套件缺乏信心,并可能消耗宝贵的时间和资源。
  3. 维护成本高:维护稳定和一致的测试环境是一项艰巨的任务,特别是在需要频繁手动设置的系统中。任何人工更改都可能破坏测试数据或环境条件,使测试的维护既昂贵又费力。
  4. 难以识别故障原因:在异步通信普遍存在的环境中,测试中的故障调试可能变得极其复杂。通常很难将故障与其真正的原因联系起来,例如消息没有发送到队列,导致系统其他部分的意外行为。这使发现和纠正问题的过程复杂化。
  5. 值交付延迟:当代码提交积累以等待通过端到端测试套件时,部署过程可能会显著延迟。持续集成中的这种延迟可能会降低交付频率,减慢向客户交付新功能或修复的速度。
  6. 缺陷识别效率低:尽管端到端测试覆盖了系统并与系统进行了大量交互,但在检测不符合业务预期的行为方面并不总是有效的。在某些情况下,即使在多次执行之后,检测到的故障数量与投入的努力和时间相比可能是不成比例的低,这就提出了关于这种方法的效率的问题。

在接下来的主题中,我们将深入探讨每一点,讨论在具有复杂集成、持续异步通信和各种业务规则的分布式环境中应该注意什么。

我们在测试上投入时间的原因不仅是为了确保代码符合预期工作,而且是为了验证我们是否符合业务目的以及软件行为是否符合业务分析师和最终用户的期望。

等待反馈:对工程团队的影响

随着端到端测试套件的扩展,完成这些测试所需的时间呈指数级增长。这种执行时间的增加不仅仅是一个技术问题;它深刻地影响工程团队、产品经理、产品所有者、执行人员和QA的工作动态。为了更好地理解影响,我们需要考虑什么是危险的:时间、价值和敏捷性。

这涉及到什么?

在软件开发中,快速反馈是关键。端到端测试旨在验证整个系统的行为,确保所有组件正确交互。然而,随着系统复杂性的增加,更多的测试被添加到套件中,这自然会导致执行时间的增加。

微服务模式的作者Chris Richardson指出,在微服务体系结构中,验证不同服务之间集成的需求可能会产生大量的测试用例。体系结构中引入的每一个新服务都增加了端到端测试的需求,以确保它与其他服务正常工作。这可能会导致端到端测试套件,尽管它的范围很广,但它会成为一个瓶颈,延迟部署并破坏微服务的主要好处之一:敏捷性。

理查森评论道:“端到端测试通常是一个瓶颈,它降低了部署频率,并破坏了使用微服务的目的。您有一个整体——部署单元——它由服务组成。或者,换句话说,一个分布式的整体。”这一关键观点强调了端到端测试的滥用或过度使用如何将本应是敏捷和模块化的体系结构转变为一个伪装的整体。

敏捷开发和持续测试的主要倡导者之一Martin Fowler也指出,“测试是质量的锚,但如果管理不当,这个锚可能会成为负担。”当端到端测试规模过大时,它们可能会产生理查森提到的那种瓶颈,从而创建一个牺牲速度和灵活性的系统。

Martin Fowler进一步认为,不平衡的测试方法可能会增加端到端测试的数量,使它们成为负担而不是优点。你的测试金字塔越高,你应该得到的测试就越少。这意味着端到端测试作为高级测试,应该谨慎使用。

Martin Fowler认为过多的端到端测试集可能表明测试流膨胀,导致测试套件维护缓慢、脆弱和昂贵。

测试套件的这种持续扩展创造了一个反馈时间延长的循环,导致工程师等待确认他们的更改没有引入新的问题。这种延迟直接影响团队的工作流程,并可能产生一些负面影响。

软件工程师面临的挑战

对于软件工程师来说,时间是至关重要的资源。当一个变更被发起时,理想情况下他们几乎会立即得到反馈。这允许在开发人员还记忆犹新的时候快速做出调整。然而,当端到端测试套件需要数小时才能运行时,这种反馈就会延迟,迫使工程师在等待结果的同时将注意力转移到其他任务上。这一背景带来了一系列挑战:

  • 上下文丢失:当反馈缓慢时,工程师可能会丢失更改的上下文。这意味着,如果检测到一个问题,他们需要花时间回忆发生了什么变化以及为什么发生了变化,这降低了效率。

  • 低效的多任务处理:为了避免等待,工程师可能会尝试在等待反馈的同时处理其他任务,但这可能会导致低效的多任务处理。通常,在复杂的任务之间切换焦点会降低工作质量,增加出错的风险。

  • 错误修复延迟:如果在长时间等待后检测到错误,立即修复就会变得困难,导致花费更多的时间来识别和修复问题,这可能会延迟开发。

对产品经理和产品负责人的影响

对于产品经理和产品所有者来说,交付新功能和修复的敏捷性对于保持竞争力和满足市场需求至关重要。当端到端测试反馈缓慢时,开发和部署周期就会延长。这可能会产生以下几个后果:

  1. 敏捷性降低:快速响应市场变化或新用户洞察的能力降低。功能需要更长的时间才能进入市场,这可能会导致错过机会。
  2. 妥协计划:较长的反馈周期使得计划下一个开发阶段变得更加困难,因为时间估计变得不那么准确。这可能会影响与利益相关者的沟通,并危及截止日期。
  3. 基于过时数据的决策:反馈的延迟可能意味着决策是基于已经发生变化的数据和情况做出的,导致不太明智的选择。

时间浪费、价值和敏捷性

等待端到端测试反馈的时间可以用来创建新功能、改进产品或修复问题。这降低了工程团队的敏捷性,降低了团队快速交付价值的能力。敏捷开发依赖于短反馈周期的前提,以允许快速和频繁的迭代。当反馈延迟时,快速迭代的能力就会受到损害,这可能会导致产品竞争力降低,不太适合市场需求。

再次引用作者兼软件顾问克里斯·理查森的话,他给了我们一个警告:

“端到端测试可能成为一个重大瓶颈,特别是在服务之间的相互依赖增加了场景和旅程的复杂性的情况下。”

如果不小心管理,这种复杂性可能会使旨在确保质量的测试套件成为阻碍敏捷性和持续价值交付的障碍。

理查森还指出,许多组织转向端到端测试,因为他们发现服务水平测试不足以确保应用程序的正确性。然而,他强调,这通常是体系结构失败的症状,在这种体系结构中,服务不是设计成独立部署的,因此需要一个端到端套件,在实践中创建一个“分布式整体”。他建议,与其盲目依赖端到端测试,解决方案应该是修复架构,减少服务的数量,并确保每个服务都可以独立部署。

马丁·福勒(Martin Fowler)在他关于持续集成的著作中也强调了“快速反馈优化”的重要性。他认为,如果没有快速的反馈,团队对问题做出反应和有效迭代的能力就会受到严重限制。他建议使用测试金字塔,其中大多数测试是单元的、快速的和廉价的,而端到端测试应该最小化以避免这些瓶颈。

快速反馈的概念并不新鲜,但它的重要性不容低估。在一个需要快速做出决策、需要快速实现更改的环境中,端到端测试的缓慢可能会使开发过程瘫痪。当反馈迅速时,工程师可以立即采取行动,纠正问题,调整功能,并继续前进。这保持了工作流程,并允许产品不断发展,同时保持相关性和竞争力。

Sam Newman对端到端测试的看法

山姆·纽曼(Sam Newman)也广泛讨论了反馈周期在测试中的重要性。他认为,在一个快速迭代至关重要的敏捷开发环境中,测试反馈时间应该尽可能短。这对于允许开发人员立即识别和修复问题至关重要,而不需要长时间等待测试结果。

对于Newman来说,长时间的反馈周期,比如那些经常与端到端测试相关的反馈周期,可能会对开发过程有害。当开发人员需要等待很长时间才能知道他们的更改是否成功时,这就会影响团队的生产力和士气。此外,长时间的反馈周期可能导致在检测到任何问题之前更改更多的代码,这进一步复杂化了调试和错误修复。

纽曼说:“这是一个很好的例子,说明了一个人如何能够在一个特定的环境中工作,而不是在一个特定的环境中工作。”在单元测试之上,它放置了集成测试,验证服务中组件之间的交互。在金字塔的顶端是端到端测试,它运行起来更慢、更昂贵,但仍然有它的位置来验证关键的系统流。这种结构允许快速获得大部分反馈,同时保持开发的敏捷性。

Sam Newman在他的《构建微服务》一书中对微服务体系结构中端到端测试的使用提出了批判性和实用的观点。他认识到,尽管端到端测试有其价值,但由于它们带来的挑战,特别是在分布式环境中,应该谨慎应用它们。

纽曼认为,端到端测试的问题在于,它们倾向于引入服务之间的耦合,这可能会阻碍这种独立性。因此,端到端测试可能成为开发周期中的瓶颈,延迟新特性和修复的部署。

纽曼分享了一个实践经验来说明这一点:

“例如,我在一个单片系统中工作,我们有4000个单元测试,1000个服务测试和60个端到端测试。我们决定,从反馈的角度来看,我们有很多端到端服务测试(后者是影响反馈循环的最严重的违法者),所以我们努力用更小范围的测试取代测试覆盖范围。”

他指出,端到端测试虽然只有60个,但被认为是延迟反馈周期的主要原因。这些测试的影响是如此之大,以至于团队选择减少大范围测试的数量,并用较小范围测试(如单元测试和集成测试)取代它们。

纽曼还指出了一个常见的反模式,他称之为“测试雪锥”或“倒金字塔”,在这种情况下,很少或没有小范围测试,而整个覆盖是通过大范围测试完成的。他指出,这种方法会导致非常慢的测试执行和很长的反馈周期,这严重损害了开发团队的效率。

他继续说:

“这些项目通常有非常慢的测试执行和非常长的反馈周期。如果将这些测试作为持续集成的一部分运行,您将不会得到很多构建,而构建时间的性质意味着,当某些东西崩溃时,构建可能会在很长一段时间内崩溃。”

Newman的这一观察强调了避免过度依赖端到端测试的必要性,特别是在微服务环境中。当大范围测试主导测试策略时,开发的敏捷性就会受到影响。运行这些测试所需的时间可能会变得如此之长,以至于当出现问题时,纠正和重新执行过程变得无效,导致长时间的停机。

对结果缺乏信心:flaky测试对产品质量的影响

开发团队在实现测试套件,特别是端到端测试时面临的最大挑战之一是结果的不一致性,通常被称为“flaky 测试”。这些测试以一种不可预测的方式失败,而且没有明显的原因,这是一个重大问题。它们可能在不同的运行中对相同的代码显示不同的结果,产生不确定性并破坏对测试套件的信任。

不稳定测试的性质

对于任何开发团队来说,“flaky 测试”都是一个令人头疼的问题,它的影响在复杂系统(如微服务体系结构)中被放大。这些测试可以在一次运行中通过,下一次运行时失败,即使没有对代码进行任何更改。它们的失败是由于不稳定的外部依赖、运行条件或同步问题等因素。好吧,但是工程界对这类问题是怎么说的呢?

山姆·纽曼(Sam Newman)警告说,“糟糕的测试”可能表明测试设计糟糕,或者被测试的系统本质上是脆弱的。当团队不能依赖他们的测试结果时,测试作为确保软件质量的工具的有效性就会降低。用于调查不能反映代码实际问题的测试失败的时间和资源被浪费了,这可能会大大减慢开发速度。下面我将留下他的一些话:

不稳定的测试是敌人。当他们失败时,他们不会告诉我们太多。我们再次运行CI构建,希望它们稍后会再次发生,结果却看到签入表堆积起来,突然间我们发现自己的功能负载被破坏了。

当我们检测到不稳定测试时,我们必须尽最大努力删除它们。否则,我们开始对一套“总是这样失败”的测试失去信心。一组不稳定的测试可能会成为黛安·沃恩所说的偏差正常化的受害者——随着时间的推移,我们可能会习惯错误的事情,以至于我们开始接受它们是正常的,而不是问题。——山姆·纽曼

马丁·福勒(Martin Fowler)在他的文章《消除测试中的非决定论》(Eradicating Non-决定论in Tests)中再次引用了这一点,他将“模糊测试”描述为有效测试套件的最大敌人之一。他说,“我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是,我的意思是。福勒还讨论了这些测试如何来自各种来源,如外部依赖(例如第三方服务)、测试环境配置中的失败,甚至不充分的时间同步。他认为,“薄弱测试”的存在是一个警告信号,表明测试策略或应用程序本身有问题。

运营成本和敏捷性

我对这个问题的看法是,在任何测试策略中都没有“flaky 测试”的余地,特别是在成本最高的测试金字塔中,如端到端测试。逻辑很简单:单元和集成测试可以快速执行,并相对容易地进行调整。然而,端到端测试在运行时间、计算资源和维护方面都很昂贵。如果端到端测试变得“薄弱”,处理它的运营成本将比单元或集成测试高出指数

这种成本不仅限于时间和财政资源;它还直接影响团队的敏捷性。当团队不能依赖测试结果时,对持续交付管道的信任就会被侵蚀。工程师们开始质疑每一个故障,不必要地重新运行测试,推迟交付。这不仅降低了新功能实现的速度,还降低了团队的士气,他们可能会感到沮丧和缺乏动力。

对最终产品质量的影响

从产品质量的角度来看,“薄片测试”的存在是一个严重的风险。当测试以不一致的方式失败时,它可能会掩盖代码中的实际问题。偶尔通过的测试可能会导致重大错误不被注意,从而导致关键的生产失败。想象一个场景,本应验证金融交易完整性的测试由于“松弛测试”而间歇性失败。如果这种行为进入生产,可能会在财务和声誉方面造成重大损害。

此外,对测试套件的信任对于代码重构和持续改进过程至关重要。如果开发人员不能相信测试会一致地捕获问题,他们可能会不愿意对代码进行必要的更改,因为害怕引入测试无法捕获的新bug。

端到端测试的可靠性对任何软件项目的成功都至关重要。当这种反馈被破坏时,整个开发周期就会受到影响。

为了解决“flaky 测试”的问题,必须采用严格的测试实践,并确保每个测试都有明确的目的,并在受控条件下运行。这可能包括消除外部依赖关系,使用mock和存根来隔离测试代码,并不断审查测试以确保它们保持相关性和有效性。

消除“flaky 检测”是一项优先事项

对于任何想要保持质量和敏捷性的开发团队来说,根除“flaky 测试”应该是首要任务。正如已经强调的那样,在有效的质量保证策略中,这些测试是没有空间的,特别是在金字塔顶端和更昂贵的测试中。对测试结果的信任是建立对代码和最终产品的信任的基础。没有这种信任,整个开发过程就会受到影响,导致产品质量较低,交付周期较长,团队效率和积极性较低。

采用更严格的测试实践,结合持续的评审和消除“flaky 测试”,将允许开发团队保持测试的完整性,并以敏捷和高效的方式继续交付高质量的软件。

高维护成本:在微服务中维持端到端环境的挑战

在任何系统中,维护稳定和一致的测试环境都是一项艰巨的任务,但这种复杂性在微服务生态系统中被放大了。在每个服务都是独立但相互连接的体系结构中,测试的维护成为一项艰巨的任务。这种努力不仅涉及手工工作和技术资源,而且在时间、质量和潜在的产品整体成功方面也会带来巨大的成本。

微服务生态系统的复杂性

在分布式体系结构的上下文中,每个服务都可以有自己的依赖项、配置和需求。这些服务通常与同一生态系统中的多个数据库、外部api和其他服务交互。为每个服务维护一个稳定的测试环境,并确保所有依赖项都正确配置和同步,这可能是一个真正的挑战。

例如,想象一个服务依赖于其他三个服务才能正常工作。如果这些依赖服务中的任何一个宕机、数据不一致或版本过时,这可能会导致测试失败,而这些失败并不能反映被测试服务的实际问题。更糟糕的是,对测试环境的手动更改,如“修复”问题的临时调整,可能会引入难以跟踪和修复的不一致。

这种情况在微服务中并不少见,因为架构的分布式特性使质量控制更加复杂。一个服务中的每一个更改都可能对其他服务产生连锁反应,这使得调试和验证非常困难。异构环境的组合和对特定配置的需求增加了维护成本,并使识别实际问题变得困难。

维护成本:时间、价值和质量

维护测试套件的成本不仅限于维护它所需的时间和财务资源。它还包括对团队持续有效交付价值能力的影响。当测试需要频繁维护时,团队可能会发现自己陷入无休止的调整和修复循环中,浪费了本可以用于实现新功能或改进的时间。

更令人担忧的是,对维护的持续需求可能导致对测试质量的忽视。当工程师和QA被维护测试环境的任务压得喘着气时,可能会有一种“不惜一切代价”通过测试的倾向,即使这意味着在质量上妥协。这种方法可能导致关键功能没有经过适当的测试,从而增加生产问题的风险。

这些失败可能是毁灭性的。想象一下,由于忽略了测试,一个支付功能发布了一个bug。这不仅直接影响用户的信任,还可能导致重大的经济损失和公司声誉的损害。

QA 的观点

对于QA来说,维护稳定的测试环境的挑战更加明显。他们通常是质量的守护者,负责确保最终产品符合要求的标准。在Glenford Myers的《软件测试的艺术》一书中,稳定和维护良好的测试环境的重要性被强调为确保准确和可靠的结果的关键。迈尔斯强调,如果没有一个受控和一致的环境,测试结果可能会误导人,导致一种错误的安全感。

作者没有直接讨论微服务,但他关于测试中维护和质量重要性的原则与这个体系结构高度相关。在分布式系统和每个服务都可以独立开发和部署的环境中,维护一个稳定和可靠的测试环境的挑战变得更加关键。

  1. 维护成本:Myers指出,维护测试套件的成本可能很高,但不维护该套件的成本更高。在微服务中,这意味着确保每个服务都可以单独测试,而不依赖于可能引入错误或不一致的手动设置。
  2. 测试环境质量:测试环境的可靠性对软件质量至关重要。这意味着测试环境需要自动和一致地配置,避免可能破坏测试数据或环境条件的人工干预。
  3. 质量影响:正如迈尔斯所指出的,测试的质量直接关系到最终产品的质量。因此,确保测试的完整性对于避免生产中的关键问题至关重要。

QA需要不断保持警惕,以确保测试环境中的更改不会破坏测试数据或引入未考虑到的新变量。设置和维护这些环境所涉及的手工工作可能是累人的,特别是在多个服务不断发展的大型组织中。

对产品经理和产品拥有者的影响

在软件工程中,一切都涉及成本。虽然产品经理和产品所有者可能不会直接参与测试的维护,但他们深受这些挑战的影响。测试的质量直接影响交付新功能的信心。当测试环境不稳定时,交付时间就会变得不确定,快速响应不断变化的市场需求的能力就会大大降低。

软件项目中的每个人都依赖于快速、可靠的反馈周期来计划和确定下一步开发步骤的优先级。如果测试难以维护并产生不稳定性,那么截止日期就会延长,在团队之间产生挫败感。这种由不断的重新规划和调整造成的持续磨损,可能会导致团队士气下降,并对有效交付价值的能力失去信心。

此外,在最后期限前完成任务的压力可能会导致匆忙的决策,在这种情况下,质量会以“敏捷性”的名义被牺牲。这就造成了一个恶性循环:生产问题导致更多的维护和调整,消耗更多的时间和资源。在这一点上,敏捷性的感知可能会变成一种错觉,在快速交付中获得的时间被浪费在解决问题上,而这些问题本可以通过更健壮的测试环境来避免。

不可避免的维护费用

在微服务环境中保持质量和稳定性是一项涉及重大成本的任务,忽视这些成本可能会导致更严重的后果。这些成本不仅仅是技术成本;他们渗透到整个组织。从努力保持测试运行的工程师和QA,到需要处理延迟和质量损失后果的产品经理和产品所有者,每个人都能感受到影响。

确保一个稳定和高效的测试环境需要一个战略性的方法。这包括在可能的情况下实现自动化,消除手动依赖,并创建可以轻松复制和一致配置的测试环境。从一开始就投资于测试质量和环境维护可能看起来成本很高,但这是一项必要的投资。不这样做的成本要高得多,无论是在时间上,还是在对最终产品和客户信心的影响上。

因此,教训很清楚:质量不能妥协。维护一个稳定和有效的测试环境的成本是不可避免的,但不保持这种质量的成本更高。确保测试是可靠的、一致的和维护良好的,对于交付一个不仅满足预期,而且能够承受时间和软件开发世界中不可避免的变化的产品至关重要。

计算端到端测试运行时间:示例

让我们举一个假设的例子,但它可以很容易地应用于许多真实的场景。假设我们正在处理一个负责管理凭证的微服务。该服务有五个主要端点:

  1. 创建优惠券 (POST /vouchers )
  2. 有效验证券(GET /vouchers/{id}/validate
  3. 应用优惠券 (POST /vouchers/{id}/apply )
  4. 取消优惠券(POST /vouchers/{id}/cancel
  5. 查询优惠券(GET /vouchers

现在,考虑对凭证应用逻辑(端点 /vouchers/{id}/apply )进行了更改。尽管更改是特定于此端点的,因为我们处理的是一个没有明确业务规则分离的遗留系统,但审慎的做法是测试所有端点,以确保更改不会在服务的其他领域引入问题。

在这样的场景中,业务规则可能非常复杂,特别是在确保凭证完整性方面。例如,用户可能需要经过身份验证或授权才能访问某些资源,一个简单的GET调用在返回结果之前可能有许多安全检查。此外,该服务可以对其他系统进行异步调用,以实时验证信息,这进一步增加了测试过程的时间。

让我们考虑端到端测试的以下细节,记住这个例子完全是假设的:

  • 每个端点的测试场景数量:每个端点有10个场景。
  • 每步平均执行时间:800ms(0.8秒)每步。
  • 每个场景的步骤数:10步(每个场景中的2步涉及异步调用或其他检查的15秒暂停)。

现在,让我们计算为该服务运行所有测试场景所需的总时间:

每个场景的时间(没有15秒的休息):

Tempo sem espera=8×0,8 segundos=6,4 segundos

以15秒为间隔的运行时间:

Tempo de espera=2×15 segundos=30 segundos

每个场景的总时间:

ááTempo total por cenário=6,4 segundos+30 segundos=36,4 segundos

一个端点所有场景的总时间(10个场景):

Tempo total por endpoint=10×36,4 segundos=364 segundos6,07 minutos

所有终端节点的总时间(5 个终端节点):

Tempo total para todos os endpoints=5×364 segundos=1820 segundos30,33 minutos

因此,为该服务运行所有端到端测试场景的总时间约为30.33分钟。虽然这在一次运行中似乎是可以管理的,但请记住,这只是一个潜在的大型微服务生态系统中的一个服务。如果我们将这种逻辑应用到一个具有许多微服务的系统上,验证所有集成所需的时间很快就会成为一个重大的瓶颈。

对生产力和开发周期的影响

当你把它乘以几个服务时会发生什么?如果测试失败需要多次运行,该怎么办?那30、33分钟它们可以在数小时内迅速转换,特别是当需要调试和重新执行测试时。

更重要的是,如果测试套件由于定义不正确的业务规则或不正确的环境配置而失败,整个开发流程可能会中断。这在遗留系统中尤其令人沮丧,因为隔离和修复问题所需的时间可能很大。

这些延迟不仅会影响工程师等待测试反馈的生产力,还会影响客户价值的交付。等待本可以优化的测试结果的每一分钟,都少了一分钟用于开发新特性或提高代码质量。

复杂业务规则

另一个需要考虑的关键点是,根据每个公司的规则,凭证服务可能有更多的错误场景和复杂的规则来确保凭证的完整性。例如,列出凭证的简单GET操作可能要求用户经过身份验证,具有特定的权限,并要求系统实时验证凭证的状态。每一次检查都增加了测试的复杂性层,并增加了验证它们所需的总时间。

此外,在异步通信普遍存在的环境中,例如使用消息队列或事件来处理数据的微服务中,测试中的故障调试可能会变得极其复杂。通常很难将故障与其真正的原因联系起来,例如消息没有发送到队列,导致系统其他部分的意外行为。

对于软件工程师和团队领导来说,测量端到端测试的运行时间并监控花费在等待或故障调试上的时间对于识别影响生产力的瓶颈至关重要。通过分析这些时间,可以更好地理解在哪里可以优化测试过程。

例如,如果测试运行时开始影响持续交付,则可能需要拆分测试套件或采用不同的测试方法来检查基本行为。我们很快就会详细讨论这个问题。

难以识别故障原因:异步环境中调试的挑战

在存在异步通信的地方,调试故障可能成为一项极其复杂和令人沮丧的任务。想象一个假设的场景,一个大型应用程序由几十个微服务组成,其中许多微服务通过RabbitMQ或Kafka等消息队列进行通信。现在,查看您正在运行端到端测试套件,以验证关键业务流程,例如处理金融事务。

支付流程

让我们想象一个支付流程。假设服务A接收支付请求,处理初始数据,然后向队列发送消息,以便服务B执行反欺诈验证。服务B在完成验证后,向服务C发送另一条消息,服务C从用户的账户中扣款并完成交易。

现在,在执行端到端测试期间,出现了故障。系统未完成付款,测试失败。但为什么会这样呢?以下是软件工程师需要回答的几个问题:

  • 服务A无法将消息发送到队列吗?
  • 或者B服务当时不可用,消息丢失或忽略了?
  • 或者C服务收到了消息,但无法访问数据库来完成借方?

这些步骤中的每一个都是异步的,并且可能独立地失败了,使识别根本原因变得复杂。

和我一起思考:你如何确定失败的确切原因?如何将未发送或接收的消息与最终事务中的错误联系起来?你会花多少时间试图追踪问题的根源?更重要的是,这将如何影响您专注于开发新功能或改进现有架构的能力?

这个场景揭示了复杂系统中端到端测试的一个陷阱。微服务的分布式特性,结合异步通信,创建了一个难以监控和调试的交互网络。当测试失败时,可能不会立即清楚问题在哪里。这不仅耗费时间,而且让工程师和质量分析师感到沮丧,他们必须处理可能不能反映代码实际问题的故障。

DLQ上的消息令人头痛

现在考虑一个特定的场景:服务A向队列发送消息,但由于某些原因——可能是错误的配置、临时网络故障或服务B不可用——该消息从未到达目的地。因此,服务B不处理交易,服务C从不收到从客户账户中扣款的命令。当测试失败时,您所看到的只是事务未完成和代理DLQ中的消息。但根本原因,一条丢失的信息,可能隐藏在表面之下好几层。

这类问题不仅难以识别;这也是一个需要纠正的头痛问题。您可以花几分钟或几个小时检查日志、再次测试和调整设置,结果发现问题在于服务之间的一个小通信故障。而且,如果这个失败是间歇性的,它可能在一些测试中没有被注意到,但在另一些测试中没有,这使得调试更加复杂。

模拟与现实

面对这一挑战,一些工程师可能会选择模拟某些服务或队列,以使测试更可预测,减少因临时不可用或配置错误造成的故障。但这里有一个重要的问题:在模拟这些交互时,我们真的是在端到端测试吗?

模拟可能适用于单元或集成测试,在这些测试中,您希望隔离组件并确保它们独立正常工作。然而,在端到端测试中,目标是验证整个业务流(包括服务之间的通信)是否在尽可能接近真实环境的环境中正常工作。如果您开始用模拟替换系统的关键部分,您将危及这些测试的完整性。

因此,请保持警惕,并始终质疑和评估是否需要使用模拟。

异步测试的时间和复杂性

考虑到异步环境中的端到端测试可能需要很长时间才能执行,对开发周期的影响是真实的。每次测试失败时,工程师都需要花时间进行研究,这减慢了新功能的交付,并可能增加降低质量的压力。此外,依赖异步通信的端到端测试可能很难并行化,进一步增加了总执行时间。

如果测试运行缓慢,且失败无法清晰识别,那么您就面临着可能危及整个敏捷开发过程的瓶颈。调试这些测试所花费的时间可以用来改进代码、重构组件或添加为业务增加价值的新特性。

异步环境中故障的隐藏成本

在异步通信系统中调试故障是一项挑战,需要谨慎、耐心和结构化的方法。识别故障根本原因的复杂性不仅会影响开发时间,还会阻碍工程师和质量分析师。不清楚问题在哪里可能会导致更长的开发周期,更大的团队挫折,并最终降低最终产品的质量。

在规划端到端测试套件时,团队考虑这些挑战并考虑如何最好地测试它们是至关重要的。模拟在某些情况下可能是有用的,但理解它们施加的限制是至关重要的。最终目标应该始终是确保系统在现实世界中按照预期工作,在现实世界中,异步通信和可能的错误是不可避免的。

价值交付延迟:端到端测试对开发周期的影响

当组织采用微服务体系结构时,主要目标之一是允许不同的团队独立工作,以持续和敏捷的方式交付价值。然而,在大型团队中,多个团队可能在相互依赖的服务上工作,端到端测试可能成为一个重要的争用点。当代码提交积累,等待通过端到端测试套件时尤其如此,导致持续集成的延迟,从而导致向客户端交付新特性或修复。

分布式环境中端到端测试的困境:利益冲突和不兼容性

想象一个大公司(如果您不再在一个公司工作),其中多个团队正在开发服务,这些服务虽然是独立的,但需要集成以交付完整的功能。每个团队都执行自己的提交,在将任何内容部署到生产中之前,都需要执行端到端测试。如果其中一个测试失败了,无论是由于配置问题、未解决的依赖关系,甚至是“糟糕的测试”,所有提交都可能停止,直到问题解决。

这就创建了一个场景,在这个场景中,一个团队的进展依赖于另一个团队测试的成功。即使一个团队已经完成了它的部分工作,如果另一个服务的测试失败,它也不能继续前进。因此,向客户交付价值被延迟,持续集成的承诺被破坏。

这种情况可能会导致团队之间的利益冲突。例如,一个团队可能已经准备好发布服务的新版本,但另一个团队仍在调整端到端测试以适应新功能或修复bug。这种类型的不兼容性可能会导致严重的延迟,即准备部署的代码被搁置,可能会持续几天甚至几周,直到所有团队都对齐。

Martin Fowler在他关于持续集成的文章中指出,持续集成的目标就是避免这种非集成代码的积累。他认为,持续集成的最大好处之一是尽早发现冲突和问题,但当端到端测试成为瓶颈时,这种好处就失去了。相反,问题是在过程的后期发现的,增加了解决问题所需的复杂性和时间。

真正的问题

假设一个团队正在一个大型微服务系统上开发一个新的多因素身份验证(MFA)登录功能。安全小组已经实现和测试的逻辑还服务,但现在结束stp的测试需要执行以确保新的功能函数和其他服务,如账户管理、通知服务和支付系统。

然而,端到端测试失败是因为MFA服务和通知系统之间的集成问题,该系统还没有更新以处理MFA发送的新消息。在此问题解决之前,任何提交都不能进入生产。因此,所有其他依赖MFA的服务,如支付服务,也被屏蔽。

这种延迟会直接影响公司快速响应遵从性需求的能力,不仅会影响客户价值的交付,还会影响整个系统的安全性。

对工程师和产品经理的影响

这种瓶颈对工程师、产品经理和产品所有者来说都是令人沮丧的。对于工程师来说,工作被他们无法控制的问题所阻碍的感觉会让他们失去动力,导致他们失去注意力。他们没有继续下一个任务,而是发现自己陷入了解决问题的困境,而这些问题往往与他们正在开发的内容没有直接关系。

对于产品经理和产品所有者来说,这些延迟可能会危及截止日期和目标,使其难以在预期的时间内向客户交付价值。他们需要处理焦虑的涉众,重新计划发布,甚至可能调整开发优先级,所有这些都是由于开发周期后期出现的问题。

优化价值交付

使用端到端测试对于确保所有服务协调工作至关重要,但当这些测试成为瓶颈时,是时候重新考虑策略了。一种可能的解决方案是集成契约和集成测试,它可以更隔离、更有效地验证服务之间的交互,允许团队独立前进,而无需等待端到端测试的结果。

Michael Feathers在《有效使用遗留代码》一书中提到,测试的真正有效性不是一次测试所有内容,而是以这样一种方式进行测试,即每个组件都在自己的上下文中得到验证。这可以减轻端到端测试的压力,并确保它们只用于验证关键业务流程,而不妨碍持续的价值交付。

缺陷识别的低效:端到端测试的困境

端到端测试在识别错误方面可能无效的说法乍一看似乎是矛盾的。毕竟,这些测试全面覆盖了系统,模拟终端用户交互并验证完整的业务流程。然而,当我们从技术和业务的角度分析时,这种看法就变得更容易理解了。

端到端测试的复杂性和缺陷识别的低效

从技术角度来看,端到端测试的编写和维护无疑是费力的。这是因为需要覆盖多个场景,并确保系统组件之间的所有可能交互都经过测试。然而,正是这种复杂性限制了它在错误检测方面的有效性。

例如,一个表面上看起来简单的特性(如更新用户配置文件信息)实际上可能有几个需要考虑的底层业务规则。你做过这样的功能吗?这听起来很简单,但在输入代码时,有几个重要的检查是绝对不能被破坏的?考虑到这一点,一个特性允许用户更新他的地址,但只有在他被验证的情况下,并且只有在没有未决事务的情况下才能更新。每个业务规则都需要测试,端到端测试应该涵盖所有这些条件。这意味着需要创建多个测试场景,每个场景都有自己的依赖关系和交互。

编写一个涵盖所有这些细微差别的端到端测试是非常困难和费力的。而且,即使测试写得很好,它仍然可能无法捕捉到某些行为,特别是那些发生在特定或罕见条件下的行为。这可能导致检测到的错误数量很低,尽管在创建和维护这些测试上投入了大量的精力。

从业务的角度来看:业务规则和沟通的清晰度

端到端测试的效率还取决于业务规则的清晰度以及开发和质量团队之间的沟通。如果业务规则没有很好地定义,或者工程师和QA对需要测试的内容没有共同的理解,测试可能会变得肤浅,无法捕捉关键细节。

在业务环境中,检测错误的低效率意味着关键问题可能会被忽略,直到它们到达生产环境,在那里修复它们的成本要高得多。此外,如果检测到的错误的回报很低,那么投入在运行这些测试上的时间和资源可能是不合理的。

这种情况提出了一个重要的问题:端到端测试真的是确保软件质量的最佳方法吗?或者其他策略,如契约测试,可以补充端到端测试,并提供一种更有效的捕获bug的方法?

马丁·福勒的观点:作为补充的契约测试

马丁·福勒(Martin Fowler)在他关于测试金字塔的著作中指出,平衡的方法可能比完全依赖端到端测试更有效。他引用了契约测试作为一种补充策略。契约测试检查不同服务之间的交互,确保满足每个服务的期望。

这种方法在微服务体系结构中特别有用,因为服务之间的通信可能很复杂,而且容易发生故障。契约测试可以帮助在与集成问题相关的错误导致端到端测试失败之前捕获它们,这可以使调试过程更加简单和高效。

另一点需要考虑的是,编写真正有效的端到端测试需要深入理解业务流程以及可能的异常和边缘条件。这可能是一个重大的挑战,特别是在复杂的系统中。试图捕获所有可能的场景可能会导致一个膨胀的测试套件,其中许多用例被肤浅地覆盖,而没有真正为验证过程增加任何有意义的价值。

因此,识别错误的端到端测试效率低下可能是多种因素组合的结果:技术复杂性、业务规则缺乏清晰度,以及编写有效覆盖所有可能场景的测试的内在困难。

契约测试:示例

对契约测试的误解

契约测试是否增加了不必要的复杂性?你听到的第一反应之一是,“这将使一切复杂化。”似乎通过添加契约测试,我们增加了额外的工作负载来保持测试运行。但实际发生的情况恰恰相反。可以将契约测试视为在服务之间分配验证责任的一种方式,从而减轻端到端测试的压力。

假设有一个相互连接的服务网络。如果没有契约测试,所有潜在的问题都必须在端到端测试中捕获,而端到端测试最终会变得繁琐和耗时。在契约测试中,这些集成失败在早期就被捕获了,远早于它们到达端到端测试阶段。

契约测试难写吗?另一个担忧是,编写契约测试是一项艰巨的任务,需要额外的努力。一开始可能看起来很复杂,特别是我们需要理解消费者和提供者的角色。但现实是,有了Pact这样的工具,这个过程就简单多了。这些工具有助于自动化契约的创建和验证,使测试过程更加顺利和高效。

例如,使用Pact,您可以为消费者和提供者创建测试。以下是它在真实场景中的工作原理:

// Teste de contrato para um consumidor frontend que consome dados de perfil de usuário
@Pact(consumer = "UserProfileFrontend")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("User with ID 123 exists")
.uponReceiving("A request to retrieve user profile details")
.path("/api/users/123")
.method("GET")
.willRespondWith()
.status(200)
.body("{\"id\": 123, \"name\": \"Alice\", \"email\": \"alice@example.com\", \"status\": \"ACTIVE\"}")
.toPact();
}

@Test
@PactTestFor(providerName = "UserProfileAPI", port = "8080")
public void testGetUserProfilePact() {
WebClient webClient = WebClient.create("http://localhost:8080");

UserProfile response = webClient.get()
.uri("/api/users/123")
.retrieve()
.bodyToMono(UserProfile.class)
.block();

assertNotNull(response);
assertEquals(123, response.getId());
assertEquals("Alice", response.getName());
assertEquals("alice@example.com", response.getEmail());
assertEquals("ACTIVE", response.getStatus());
}

在这里,测试正在验证消费者(在本例中是前端)是否从提供者(用户API)接收到正确的数据。提供商将检查此契约,以确保API返回正确的信息。

小型生态系统不需要合同测试吗?一些工程师认为,如果微服务生态系统很小,合同测试是一种不必要的奢侈。但这是一种短视的观点。随着系统的发展,缺乏合同可能会导致通信故障,而这些故障只有在后期阶段才会被检测到,比如端到端测试,或者更糟的是,在生产中。

即使在小环境中,从一开始就引入契约测试也是有益的。这不仅建立了良好的实践,也为更有组织、更安全的增长奠定了基础。

采用契约测试的担忧

消费者和提供者的维护:最常见的担忧之一是需要维护合同的双方:消费者和提供者。是的,这意味着需要额外的努力。然而,维护可以高度自动化,并集成到ic /CD管道中。这里真正的好处是契约测试提供的可见性。当服务发生变化时,契约有助于快速识别哪些用户将受到影响,从而促进团队之间的协调。

灵活性的丧失:另一个担忧是,合同测试可能会限制创新或快速变化的能力。但合同的设计是为了在可接受的范围内提供灵活性。可以引入新的契约,而保留以前的版本,直到所有消费者都准备好进行转换。这使得服务的发展不受干扰。

担心初始开销:在已经在生产中的系统上实现契约测试似乎是一项艰巨的任务。许多人宁愿推迟到“更合适的时间”,而这往往永远不会到来。事实是,契约测试不需要立即实施。它们可以逐步引入,从最关键的服务或新功能开始。

为了更好地说明这一点,考虑一个消费者API的例子:

// Teste de contrato para um consumidor API que processa pedidos de compra
@Pact(consumer = "OrderProcessingService")
public RequestResponsePact createOrderProcessingPact(PactDslWithProvider builder) {
return builder
.given("Product with ID 456 is available in stock")
.uponReceiving("A request to place an order for a product")
.path("/api/orders")
.method("POST")
.body("{\"productId\": 456, \"quantity\": 3, \"userId\": 789}")
.willRespondWith()
.status(201)
.body("{\"orderId\": 1010, \"status\": \"CONFIRMED\", \"estimatedDelivery\": \"2024-09-15\"}")
.toPact();
}

@Test
@PactTestFor(providerName = "OrderAPIProvider", port = "8080")
public void testCreateOrderPact() {
WebClient webClient = WebClient.create("http://localhost:8080");

OrderResponse response = webClient.post()
.uri("/api/orders")
.bodyValue(new OrderRequest(456, 3, 789))
.retrieve()
.bodyToMono(OrderResponse.class)
.block();

assertNotNull(response);
assertEquals(1010, response.getOrderId());
assertEquals("CONFIRMED", response.getStatus());
assertEquals("2024-09-15", response.getEstimatedDelivery());
}

在第二个示例中,我们有一个请求处理服务,它使用API来创建新请求。该契约确保,当为库存产品创建订单时,API将响应“ CONFIRMED ”状态和估计交付日期,从而验证事务的完整性。

通过克服最初的恐惧,合同测试提供了无可争议的优势。它们允许早期发现问题,促进团队之间的沟通,并减少端到端测试的负担。随着时间的推移,它们可以成为开发周期中不可分割和有价值的一部分。

当然,采用合同测试需要最初的努力,但这种投资在稳定性、可靠性和以安全方式扩展系统的能力方面带来了回报。对于那些仍然不情愿的人来说,最好的方法是从小处开始,提供关键的服务,然后随着团队在实践中获得信心而扩展。

但你可能更喜欢多种测试策略的方法。

组合方法:契约测试和验收测试

当我们谈到确保复杂系统的质量时,我们需要各种有效和全面的测试策略。以Nubank为例,随着公司的发展,这个挑战变得明显起来,他们意识到他们对端到端测试的依赖正在成为一个主要的瓶颈。为此,Nubank采用了合同测试和验收测试的组合策略,这被证明更有效和可扩展。

在一篇题为“为什么我们杀死了我们的端到端测试套件:Nubank如何切换到合同和验收测试策略,以扩大到超过1k工程师”的文章中,Nubank详细介绍了它在完全依赖端到端测试套件时所面临的问题。通过覆盖大量端到端场景的测试套件,他们开始注意到一系列挑战:

  • 测试执行缓慢:随着代码库的扩展,端到端测试的执行时间越来越长。这减慢了反馈循环,影响了团队的敏捷性。
  • 结果缺乏可靠性:许多端到端测试不稳定,失败不一致,产生大量误报,降低了团队对测试套件的信心。
  • 维护成本高:维护端到端测试套件是一项艰苦而昂贵的任务,特别是随着微服务数量的增加和它们之间的交互变得更加复杂。

面对这些问题(我们已经在本文中讨论过),Nubank决定将其方法转向合同测试和验收的结合。

什么是验收测试?

验收测试旨在验证系统或功能是否满足业务需求和涉众的期望。他们专注于验证软件是否满足先前定义的验收标准,模拟真实的使用场景,以确保一切按照预期工作。

验收测试的结构通常包括以下要素:

  1. 验收标准:与涉众(如产品负责人、业务分析师和软件工程师)合作定义。它们精确地指定了需要验证的内容,以便认为功能是完整的并准备交付。
  2. 测试环境:验收测试在尽可能接近生产环境的环境中执行。这确保了测试结果能代表系统的实际行为。
  3. 测试场景:每个验收标准被转换成一个或多个测试场景,逐步描述用户与系统的交互。这些场景可能包括不同的变量,如用户类型、权限或特定条件。
  4. 执行和验证:然后执行测试场景,并将结果与验收标准进行比较,以确定功能是否正确实现。

例如,在航班预订系统中,验收测试可以确保在选择航班和应用代金券时,正确应用了折扣,预订了座位,并向用户发送了确认。这些测试对于确保业务规则得到满足以及最终产品符合涉众的期望至关重要。

综合策略

这种组合方法也可以应用于其他环境。假设您正在使用一个负责管理凭证的微服务,就像我们前面讨论的那样。通过结合合同、验收和端到端测试,您可以:

  • 契约测试:确保凭证服务API与其他服务(如身份验证或支付)正常工作,验证所有必要的参数都存在并正确格式化。
  • 验收测试:验证是否遵循了关键的业务规则,例如确保凭证不能应用于已经完成的购买,或者用户只能在经过身份验证的情况下应用凭证。
  • 端到端测试:通过应用代金券验证整个购买流程,从产品选择到付款确认,确保流程的所有步骤按照预期一起工作。

Nubank决定减少对端到端测试的依赖,并采用合同和验收测试的组合策略,这对有效地扩展代码库和工程团队至关重要。这一举动不仅提高了测试的效率,而且使公司在继续增长的同时保持了软件的质量。

对于金融公司和其他面临类似挑战的组织来说,经验教训是,组合测试方法可以提供一种更有效的方法来确保软件质量,减少瓶颈,并保持持续向客户交付价值所需的敏捷性。

结论

端到端测试是否在微服务中是一个问题,以至于我们建议完全排除它?答案并不简单,正如我们在本文中讨论的那样,这不是攻击端到端策略的问题。相反,我们强调,这种方法如果使用不当,可能会带来重大挑战,特别是在复杂的体系结构中。

端到端测试在开发过程中占有一席之地,因为它们提供了不同系统组件如何一起工作以满足业务流程的全面视图。然而,当它们被视为保证质量的最终解决方案时,问题就出现了。正如我们从著名工程师的意见和Nubank的成功案例中所看到的,简单地颠倒测试金字塔,将大部分信任放在端到端测试上,并不一定会带来更高的质量或信任。

对端到端测试挑战的思考:

  1. 复杂性和成本:端到端测试本质上是复杂和昂贵的维护。它们涉及多种依赖关系,并经常遭受不稳定性,如“模糊测试”,这可能导致假阳性或阴性。这不仅会消耗时间和资源,还会影响开发团队的敏捷性。
  2. 慢反馈:增加对端到端测试的依赖可能会导致慢反馈周期。团队不会收到关于代码更改的快速响应,而是要等待数小时才能运行整个测试套件。这种延迟可能会危及快速迭代和交付持续价值的能力。
  3. 信任和质量:颠倒测试金字塔和过度依赖端到端可能会产生一种错误的安全感。尽管这些测试验证了整个系统的行为,但它们并不总是捕获细粒度级别的故障,比如微服务之间的集成问题,这些问题可以通过契约或单元测试更容易识别。
  4. 注意业务场景:端到端测试应该保留用于验证关键业务流程,而不是覆盖所有可能的场景。通过将端到端的重点放在核心测试上,同时将集成和基本行为检查委托给契约和单元测试,我们实现了更有效、更不容易出现瓶颈的测试策略。

需要思考的问题:

  • 团队是否有效地使用端到端测试,还是它们正在成为价值交付的瓶颈?
  • 如何平衡单元、契约和端到端测试之间的负载,以获得快速反馈并在不牺牲敏捷性的情况下保持质量?
  • 是否有机会采用Nubank所实现的实践,将合同测试与端到端连接起来,以实现更流畅、更可靠的开发周期?

端到端测试本身并不是问题;问题在于它们如何在复杂系统(如微服务系统)中应用和重载。它们是一个强大的工具,但是,像任何工具一样,它们的使用应该仔细考虑,并与其他测试方法进行平衡。通过集成合同和单元测试,并为真正关键的业务场景保留端到端测试,我们可以保持开发的敏捷性,并确保最终产品是健壮和可靠的。

如何在体系结构中应用端到端测试的决定取决于特定的上下文和您所面临的挑战。为了定义最适合您的现实的测试策略,反思您的系统目标、业务需求和团队结构是很重要的。答案不在于消除端到端测试,而在于谨慎而明智地使用它,作为平衡测试策略的一部分。

软件质量保障
所寫即所思|一个阿里质量人对测试技术的思考。
 最新文章