• 0
  • 0
分享
  • 使用契约先行开发减少契约测试​​——软件测试圈
  • quinn 2024-09-04 16:00:47 字数 3844 阅读 504 收藏 0

  契约维护的难题

  如今微服务凭借其灵活、易开发、易扩展等优势深入人心,不同服务之间的集成和交互日渐繁多且复杂。这些服务之间交互的方式是多样的,常见的有 HTTP 请求和消息队列。在它们交互的过程中,会有服务的版本演进,交互信息的格式或方式就会产生变化,前后版本的接口可能并不兼容,甚至开发环境经常会宕机更新,加之不同服务的开发进度有快有慢,各团队的优先级有高有低,在开发过程中,服务间交互方式的匹配性就成了一个问题。

  这里,不同团队之间,对服务间如何进行发送和接受消息所能达成的共同理解,我们称之为契约 (contract)。如何采用一个合理的机制,维护服务间契约,使服务提供方和消费房能够在不造成事故的前提下,保持各自的高效开发,越来越成为各团队日常开发中要面对的问题。

  契约测试

  契约测试 (contract testing) 就是在这样的背景下应运而生,以下引用 Pact 官网的定义:

  Contract testing is a technique for testing an integration point by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a "contract".

  契约测试是一种测试集成点的技术,它通过隔离检查每个应用程序,以确保其发送或接收的消息,符合记录在“契约”中的共同理解。

  也就是说,在测试己方服务时,通过使用测试替身 (test double),让它能够模仿我们所依赖的外界系统,返回相对真实的消息响应,从而让我方团队在尽可能保证与外界系统兼容的前提下,避免受到外界系统宕机或开发新版本等影响,提升开发效率。再结合消费者驱动开发的优势,可以避免服务提供端浪费精力去实现不必要的功能,因此,很多团队采用了消费者驱动的契约测试 (consumer-driven contract test) 的实践。

1-1.png

  在契约测试的帮助下,很多团队真正提升了开发效率,掌握了自己的节奏,但也有些团队发现效果并不明显,因为契约测试带来的收益并不是免费的。契约测试有着不少的开发成本,每有一个新的需求,新的接口,新的字段,或是老字段的可空性发生了改变,以及枚举值的增加或减少,都需要增加或减少一些测试用例,然后在测试的过程中生成新的契约。这些契约大多以 OpenAPI 文档的形式存在,作为两个团队日后讨论的基准。

  契约测试驱动的合作流程

  消费方与提供方沟通,达成基本契约:增加一个接口,调用的时候传一个 RequestDto,接口返回一个 ResponseDto,其中 RequestDto 和 ResponseDto 哪几个字段要非空。

  消费方回去写自己的契约测试,生成契约 (通常以 OpenApi doc 形式),然后以契约测试驱动,开发自己的逻辑。

  服务方拿到生成的契约,进行测试驱动开发,验证契约是否被满足。

1-2.png

  契约测试有时修改代价高

  在使用契约测试时经常有这样的感受:比如,一些简单的契约,比如非空字段和格式校验等,每种情况都要专门的测试用例,而实现它却只需要一个注解,有点得不偿失。

  更重要的是,随着测试的编写,生成的契约可能比当时商讨的更为简单,比如一些 400, 401 等情况,有时并不会为每一个 API 写足够细节足够详尽的测试;也可能生成的契约比商讨的更为详细,比如消费方在编写契约测试的过程中考虑到了更多的边缘场景。因而每当由以上情况导致修改契约时,我们都会重新沟通,再等待消费方重新写契约测试,再生成契约,然后服务提供方再开发。这个从沟通到落地的闭环比较长,每次修改时,服务提供方需要等待消费方编写测试,生成契约,然后消费方等待服务方按照契约开发完成,才能发布新的版本,这些等待都会拉低开发效率。

  解决的思路

  反馈周期长是我们经常需要面对的问题,比如我们要搞快速迭代,定期 showcase,就是为了及时得到客户的反馈;再比如我们搞结对编程,也是为了将 pull request 上可能出现的沟通提前,避免盲目开始之后又造成返工。这个时候我们再看开发流程时就会想,如果第一步的沟通可以直接产出固化的契约,足够直观和详尽,让双方及早沟通,同时又可以使用自动化的方式约束消费方和服务方双方,省掉重复琐碎的契约测试,让沟通过后双方都可以直接开始开发就好了。

  契约先行开发

  这里说的契约先行,指的是在写所有代码之前,把我们沟通好的契约手写出来,或者通过一些图形化工具生成出来,比如手写或生成一个 yaml 格式的 OpenAPI 文档,这样沟通的产出足够直观。把这个文档放在代码库中维护,然后以它为依据,通过自动化流水线生成各方的沟通组件(sdk),既能使双方同时开始开发,又能对双方进行约束。

  以下以 Java + Spring 为例,通过使用 OpenAPI generator 工具生成代码,达到以上效果。

  对于消费方来说,流水线可以根据定好的契约,生成封装好的 client 类,它提供一个简单的方法,这个方法包含了 RestTemplate 的参数和逻辑,以及对应的 RequestDto/ResponseDto。消费方开发人员只需要关注己方业务需要,将合适的参数传给该方法,无需关注这些参数是用于 path 还是用于 body,该方法会以合适的方式与服务方沟通。同时我们可以定制化生成的方法,对非空字段进行校验,达到约束效果。

  对服务方来说,流水线可以生成对应的 Controller 组件,http path 和方法的匹配等,服务方只需要复写相应的方法来完成自己的业务需求,无需关心传进来的参数是属于 path、header 或者是 body。由于定制化生成的 server 端 sdk 严格根据契约生成了合适的注解,比如 @NotNull?, @Size?, @Pattern 等,Spring 可以自动对注解进行校验,server 端就可以自动拒绝不符合契约的请求。同时,由于生成的 ResponseDto 也带有响应 validation 注解,我们也可以对服务端返回的 ResponseDto 进行约束。

  契约先行模式下,团队的沟通闭环

  这样以来,团队的合作流程如下:

  1. 消费方与提供方沟通,达成契约,并在契约代码库中一起提交契约代码,即 OpenAPI doc。然后触发流水线,生成各方代码 sdk。

  2. 双方各自引入 sdk 进入开发。

1-3.png

  通过观察以上流程可以发现,与契约测试相比,该流程提前对沟通结果进行了直观的固化,使双方基于细节的沟通提前,从而将反馈周期缩短,减少返工概率。另外,契约一旦提交,自动生成的 server 端和 client 端 sdk 也同时可用,消除了开发过程中消费方和服务方之间的依赖,两端可以并行开发,减少等待时间,提升开发效率。

  如果在开发的过程中,哪一方再出现细节问题,需要调整契约时,可以尽早找到另一方进行讨论。此时由于双方都已经进入开发,都了解一些相应的细节,讨论内容更加具体,更加高效,而且讨论产生的契约变动也会更早产生效果。

  契约先行的适用场景

  契约先行开发并非银弹,它在解决特定场景下的问题时,才更“划得来”。

  比如契约应简单直接。 一些非空校验,格式要求,简单的字段间匹配,使用契约先行和生成代码都是低投入高回报,生成的代码具有非常好的约束性。但是如果契约中包含了丰富的业务逻辑,不容易在单个 OpenAPI doc 中描述的,还是手写契约测试更加明晰,维护性也更好。

  再比如我们使用的编程语言或框架需要得到 OpenAPI generator 良好的支持。 如果不能根据契约生成好用的代码,或者在生成代码的过程中需要做过多的定制化,那么该方法可能并不适用,或者并不划算。

  在开发过程中需要有健全的集成测试或者组件测试。 上述生成的代码中,虽然 sdk 可以对服务间的通信进行合法性约束,但是很多单元测试并不能提前发现问题。比如在 server 端,我们生成的 @NotNull 等注解,需要把 Spring 启动,且测试用例对业务逻辑具有足够的覆盖,才能及早发现问题,避免线上报错。

  契约先行的成本

  天下没有免费的午餐,在带来上述优势的同时,契约先行开发也会带来一些成本。

  最主要的成本是 OpenAPI generator 的学习成本。目前 OpenAPI generator 虽然已经可以支撑大多数的语言和框架,但是要做到足够好用,还需要对生成代码进行一些定制化,这些定制化需要一些时间投入。

  结论

  在服务间合作开发的过程中,为了维护契约的有效性,适用契约测试可以让不同团队之间的开发在一定程度上解耦。在一定场景下,使用契约先行的合作方式可能更高效,比如契约足够简单直接,开发使用的技术适用于生成的代码,开发过程中已经有足够的集成测试或组件测试时,契约先行可以缩短团队间的反馈闭环,减少等待时间,提升开发效率。


作者:Thoughtworks洞见    

来源:http://www.51testing.com/html/80/n-7795080.html

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 每当一个系统上线开始运维以后,项目组就会进行到一个阶段,不断的收到从线上反馈回来的生产事件,对生产事件进行有效的深度挖掘、分析,形成正确的改进项,可以持续的完善系统和研发管理过程。常用的缺陷分析方法有:根本原因缺陷分析法、四象限缺陷分析法、ODC 缺陷分析法、Rayleigh缺陷分析法和Gompertz 缺陷分析法。ODC(Orthogonal Defect Classification,正交缺陷分类)是获取缺陷的一种分类方案,但它不仅仅是一个分类方案,还是一个软件过程的度量系统,它是建立在包含于缺陷流中的语义信息基础上的,可以帮助评估测试效率,对错误进行跟踪,通过方案的分析机制可以评估客户的...
            0 1 4949
            分享
          •   背景  单元测试是项目研发的重要阶段。相比接口测试、界面测试,单元测试外部依赖最少、颗粒度最细,可以在开发阶段更快、更直接地发现Bug,适合开发团队快速排查错误,是保障研发质量的重要手段之一。  但是,随着单元测试用例数量的增加,执行速度也会逐渐变长。我们经常看到一个现象:在研发人员的研发终端上,运行数千条单元测试脚本,时间超过1个小时。  本文主要是总结了笔者近年来的单元测试经验,分享如何有效提升单元测试的运行速度,节约开发时间。  提升妙招  1、优化单元测试代码  例如:    @Test     pub...
            0 0 1224
            分享
          • 软件包含哪些内容数据 程序 文档软件生命周期需求分析概要设计详细设计编码测试验收瀑布模型计划需求分析设计编码测试运行-维护螺旋模型螺旋模型是一种演化软件开发过程模型,它兼顾了快速原型的迭代的特征以及瀑布模型的系统化与严格监控。测试流程获取测试需求编写测试计划指定测试方案开发于设计测试用例执行测试提交缺陷报告测试分析于评审提交测试总结准备下一版本测试软件测试过程模型V模型W模型H模型H模型适合外包测试公司 来一个需求用户需要提供软件,需求,设计,标准大型中上型的测试部门都是独立的优点早准备,早执行,效率高X模型X模型还定位了探索性测试,这是不进行事先计划的特殊类型的测试,这一方式往往能帮助有经验...
            0 0 4480
            分享
          •   职场这个地方,既是追名逐利的斗兽场,又是恩怨交织的武侠江湖。总有人调侃说,甭管你进了500强还是挤进一线互联网大厂,办公室的政治斗争照样精彩纷呈,不带打折的。身为一个只想安静码代码的程序猿,咱压根不想掺和这些勾心斗角的事儿,可现实往往是,人在江湖飘,哪能不选边站。  "我只要做好自己就行了",这句话听起来清新脱俗,宛如职场的一股清流,仿佛只要专注于个人工作,就能在这个波涛汹涌的职场江湖中安然航行。然而,现实生活中的公司环境往往错综复杂,特别是在那些架构庞大、部门林立的一线互联网大厂中,人际关系和派系斗争如同一张看不见的大网,悄无声息地覆盖在每一个角落。  我有两个颇具才...
            0 0 275
            分享
          • 1、接口测试接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系等。由于如今的系统复杂度不断上升,传统的测试方法成本急剧增加且测试效率大幅下降,所以就要做接口测试。同时,接口测试相对容易实现自动化持续集成,且相对UI自动化也比较稳定,可以减少人工回归测试人力成本与时间,缩短测试周期,支持后端快速发版需求。接口持续集成是为什么能低成本高收益的根源。现在很多系统前后端架构是分离的,从安全层面来说,只依赖前端进行限制已经完全不能满足系统的安全要求(绕过前面实在太容易...
            11 11 1086
            分享
      • 51testing软件测试圈微信