其他分享
首页 > 其他分享> > 20210407 契约测试简介

20210407 契约测试简介

作者:互联网

一、为什么要做契约测试
    ● 验证成本高
    ● 测试结果不稳定
    ● 测试反馈时间周期长
       对于微服务项目,从单元测试到整体的测试,不同微服务之间的协同和交互,应该怎么做?如果采用传统的总体测试方法,对服务之间的协议进行验证,那么随着服务数量和依赖关系复杂度的增加,将会面临指数级增长的挑战,当对一个服务进行测试时,比如做烤鱼,厨师首先需要有一条鱼,还需要相应的调料、配菜、天然气和操作间的服务;这么多服务,每一个服务都需要单独搭建自己的项目环境,每个服务都要执行部署,配置,确保每个服务都正常运行;这种验证成本是很高的,因为每个服务的运行都不是那么容易的;构建项目时,微服务项目本身是分布式系统,服务通信是跨网络的调用,服务间的协作,网络延时等因素都会影响测试结果,这就会导致测试结果不够稳定。测试发现问题时,需要对每个服务依次定位,因此问题的反馈时间会被拉长,这就带来微服务测试反馈时间长的问题;所以,如何提升服务间协同测试的有效性,如何提升效率,这就是项目服务规模化后所必然面对的挑战;而契约测试能够帮助简化测试流程,帮助提升反馈时间,帮助解决测试结果不稳定,帮助提高测试覆盖率。

二、什么是契约测试
    ● 契约的概念
          ○ 请求
      请求头、请求url、请求的动作,请求的参数  (请求的动作,比如 put、get、post、delete 等这些东西,请求的参数可以是 get 的string 也可以是 post 的body)
          ○ 响应
      生产者服务对于前面请求的应答。包含响应的状态码、响应的内容以及错误描述等信息
          ○ 元数据
      指对消费者和生产者之间的协作过程的描述。比如生产者消费者的名称、上下文以及场景描述等
    ● 契约测试(Contract Test)
          ○ 基于契约,对消费者与生产者间的协作的验证。本质上就是验证生产者所提供的内容是否满足消费者的期望。
       契约首先就是消费者服务和生产者服务之间协作的公约,在微服务架构下,任意两个服务之间都有可能存在依赖关系;假如一个服务需要从另外一个服务获取数据,两个服务就涉及到协作关系,而协作的约定,实质上就是契约;契约通常是由两个服务之间进行联系和沟通的请求和响应以及元数据组成;请求包含请求头,url 和请求方式,以及请求参数;响应主要作为生产者服务针对发送请求者的应答,一般包含响应状态码,响应内容以及错误信息的描述;元数据是契约里对消费者与生产者之间,一次协作过程的描述,比如消费者和生产者的名称,上下文或者场景等描述信息,这就是元数据;
       契约最主要的组成部分,就是请求和响应,两个服务沟通通信采用的是 rest api,而 rest api 是基于 http 协议;了解什么是契约后,契约测试又是什么?顾名思义,就是对契约的验证工作;契约测试准确的定义是,基于契约对生产者和消费者之间的协作进行验证,通过契约测试就能够将契约作为两者协作的中间标准;进行契约测试的重要目的就是验证生产者所能提供的内容是否能够满足消费者所期望获得的东西;如果提供的内容不满足,就意味着契约的破坏,这样消费者可能就无法正常进行自己的业务逻辑,这就是契约测试的目的
    ● 分为两种类型
          ○ 消费者驱动的契约测试
          ○ 生产者驱动的契约测试
    ● 常见的就是消费者驱动的契约测试 (Consumer Driven Contract Test)简称CDC。
       契约测试在行业内,主要分为两种类型,消费者驱动的契约测试和生产者驱动的契约测试,最常见的就是消费者驱动的契约测试,简称 CDC(Consumer Driven Contract Test);消费者驱动的契约测试的核心思想是站在消费者服务端的角度,根据消费者想要的数据格式和交互细节,按消费者的需求生成一份契约文件;作为生产者根据契约文件,实现自己的逻辑,并且需要在持续集成环境中,持续验证实现结果是否正确。
1.png       如上图,左边是消费者服务,右边是生产者服务,两个服务之间通过 http 请求沟通,消费者服务在业务逻辑实现过程中,需要生产者提供数据内容;中间就是消费者驱动契约测试时,根据消费者提出的数据或者结构的要求而生成的契约文件;生产者按照消费者的需求实现业务逻辑,在持续集成环境里,生产者反复验证生产者的实现结果是否满足契约;生产者的业务逻辑无论怎样扩展,对消费者是无关的,只是按照契约文件提供数据,满足需求;这样做的好处是生产者不用受到限制,唯一的限制就是这份契约,只要满足契约,不论生产者怎么做,都不会影响消费者;对消费者而言,只要满足契约要求,就是合格的,别的并不关心,这就是消费者驱动的契约测试。
    ● 接口破坏的例子
01.png       此案例反应的是契约被破坏的情况,上面消费者服务和生产者服务之间通过 v0 契约进行沟通,消费者希望获得 v0 这样的请求响应和元数据,v0版的接口就按相应的要求提供数据;在业务扩展时,如果没有注意到消费者的契约,修改了对外提供的接口,改动成为 v1版接口,下面的 v1 版接口所能提供的相应或者接收请求的内容将发生变化;消费者服务的请求不能得到相应的期望数据,消费者服务就无法完好的提供自己的服务;生产者服务的接口从 v0 变成了 v1,中间发生了变化,而 v0 和 v1 没有兼容关系,这就是契约 v0 被破坏;这时引入契约测试,就能及时帮助生产者服务开发团队,发现这样的问题,及时进行修整,确保v1版本的接口依然能够和v0的契约进行兼容。
    ● CDC实现的核心流程
         1. 对消费者的业务逻辑进行验证,对它所期望的响应设计一个模拟生产者(Mock),并将消费者请求与模拟生产者的响应协作的过程记录为契约文件
         2. 通过将契约文件对生产者进行回放,从而验证生产者提供的响应内容是否满足消费者的期望。
02.png       CDC的核心流程,主要是两个步骤,第一个是消费者端,对消费者业务逻辑进行验证时,先对其期望响应做一个模拟提供者;利用模拟提供者模拟生产者服务,像生产者那样响应数据,然后将消费者请求和模拟提供者响应数据的协作过程记录为契约,
在这个过程中,将协作过程记录为契约文件,契约文件一般是 json 格式;这里面的响应是模拟提供者响应数据的协作过程;第二步,通过契约文件对生产者进行回放,回放过程中验证生产者所能提供的内容是否满足消费者的期望;
       图中,消费者向模拟生产者发出请求,模拟生产者按照预期约定发送响应,模拟生产者把中间协作过程,也就是数据,汇聚记录成一份文件,就是契约文件;左侧的测试完成后,开始第二步,模拟生产者现在再一次模拟消费者,利用之前记录的契约文件向生产者发送请求,生产者会返回一个响应;模拟消费者根据契约文件,判断响应是否满足,这个契约文件是在消费者测验证过的,拿这个文件和真正生产者的响应进行验证,如果满足,契约测试通过;如果不满足,测试失败,意味着生产者当前所做的修改会产生契约破坏,消费者服务无法正常获得期望的响应。
    ● CDC的核心原则
          ○ CDC是从消费者角度来确定提出契约,然后交给生产者实现,最后以测试用例对契约进行约束。生产者在满足测试用例的情况下,可以自行修改实现接口,而不影响消费者。
          ○ CDC是从利益相关者的角度出发,最大限度的满足需求者的业务价值实现。
          ○ CDC不同于组件测试,不需要深入测试消费者服务的功能,它更侧重于检查服务之间的输入输出是否包含了必要的数据结构和属性。
       这样的过程反应了 CDC 的几个核心原则,CDC 是以消费者提出 接口契约,以 提供者 实现,并且以测试用例对契约产生约束;所以 提供者在满足测试用例的情况下,可以自行更改接口或者架构的使用方法,不会影响消费者;CDC是针对外部接口进行的测试,能够验证服务是否满足消费者期待的期望,本质是从消费者的目标和动机出发,最大限度满足消费者业务价值的实现;这和TDD测试驱动开发,BDD行为驱动开发的设计思路是相似的,都是建立在需求方的利益目标的基础上考虑如何满足需求方;
契约测试不是组件测试,契约测试的重点是契约是否被满足,并不关注收到响应内容后,服务内部的业务逻辑有没有问题,契约测试关注的是生产者服务的输入输出是否包含必要的数据结构;请求和响应的数据内容和数据结构是否正确,关注请求和响应本身,契约测试并不关注消费者服务收到内容后的处理方式,关注的是收到的是什么内容;契约测试可以帮助消费者的开发团队确认协作没有问题,也能够帮助生产者服务的开发团队在开发过程中确认契约测试的结果,从而确认自己的改动是否会对消费者服务产生不利的影响;在开发团队设计新服务时,CDC也是非常有用的,开发人员可以通过一系列的契约测试用例,界定他们需要提供的响应内容是否能够满足消费者,从而决定自己 api 的设计方法;这就是 CDC 的核心原则和流程。

三、契约测试的价值
契约测试它测试的对象是生产者服务,但是它的价值却体现在消费者服务上。
       消费者驱动的对象是契约,而不是契约测试,当某一生产者服务上线后,消费者服务需要消费生产者服务,按契约测试要求,消费者应该提出按期望建立怎样的契约,从而建立契约测试;在契约测试的形势上,契约测试的测试对象是契约,通过对生产者服务所接受的请求或者能提供的响应进行测试;这样的测试对提出这份契约的消费者有利,价值上表面是在测试生产者服务,但保证的是消费者服务业务逻辑的正常开展;现实企业中,原本的单体式架构应用被分解为很多微服务,每一个微服务都有一个团队,这些团队每天关注更多的是实现自己的业务逻辑;他们对外提供的服务并不确定,所以如果作为使用者,如果不关注希望获得什么样的内容和结构,那么生产者能够时刻关注这些吗?提供者面对很多消费者,没办法准确把控每个消费者自己需求的东西;所以契约测试,更多需要消费者自己关注自己的利益。
03.png       左边是一个生产者服务,上面是一个生产者服务,下面是三个消费者服务,每个消费者服务消费生产者所能提供的服务数据的一部分,各取所需;生产者能够提供三个简单的字段,第一个是 id 第二个是 name 第三个 是 age;三个字段整个是一个 json 格式的数据;下方的消费者服务 A,对于生产者提供的服务,只用到了 id 和 name,就是说在 消费 id 和 name;消费者服务 B,消费 id 和 age,name对它而言,没有价值,有价值的只有 id 和 age;消费者服务 C,消费 id name 和 age 3个字段;三个服务 所期望的东西不同;id name 和 age 这类 json 只能算作规约,并不是一个契约;因为契约 一般都是要成对存在的;生产者服务提供的三个字段只是一个规约,比如合同是早就写好的,但是在双方签字之前,合同是没有意义的,只有双方都签字,按手印,盖章后;这时的合同就不是要约了,而是一个具有法律效应的合同;同理,这里生产者提供的三个字段,也只是一个规约,针对消费者A,消费者 A的契约 就是由 两个字段,id 和 name 组成的 json;消费者 B 的契约,也是 两个字段,id 和 age 组成的 json 字段; 消费者 C 的契约是由 id name age 三个字段组成;左侧的图,三份契约都是可以满足的,所以消费者 A B C 可以和平共处,都可以从生产者获得自身所需的东西。
       业务规模扩大后,这时消费者 A 可能产生一个需求,希望生产者能够提供更加详细的用户姓名信息,希望里面包含 姓和名,这个需求对于生产者而言并不难,所以生产者为了满足消费者 A,就提出了这样的规约,右边的图中,name的字段值,被分为 名和姓,这样的修改,对消费者A 正合适,消费者 B 只消费 id 和 age,不需要名字,所以消费者 B 无所谓;比较麻烦的是消费者 C,生产者改变了 原有的契约;对于消费者 C,这是不可接受的,是一个典型的 契约破坏。
       有了契约测试以后,生产者服务尝试做出这样的改变后,就会提前发现这样的问题,发现问题后,有两种选择,一是不做改变,二是去和消费者 C 协调兼容性问题,当然这就不是契约测试所关注的了,通过这个案例,可以发现,契约测试的价值点:
       1) 左边的图,消费者 A 没有使用 age 消费者 B 没使用 name;基于消费者驱动的契约测试,契约的内容是由消费者提供的,契约内容体现消费者对生产者提供规约的消费需求,注意生产者提供的只能叫规约;这里的需求不仅包含消费者需要什么,也包含消费者不需要什么,因为当生产者提供规约的某一部分,不被任何消费者消费时,意味着生产者对这部分内容可以随意修改了,不用担心会影响消费者;这也是契约测试一个非常重要的价值点。
       2) 单个生产者对多个消费者,要最大化体现契约测试不同于集成测试的价值,一定是在单个生产者对应多个消费者的架构来说的;因为在只有一个消费者和一个生产者的架构下,只存在一份契约;契约是成对的,必然有一个生产者和消费者,一份契约容易被发现,就不会出现契约被破坏的情况,因为契约是消费者提出的,生产者进行相应修改后,通知唯一的消费者是比较容易的;这种情况下,集成测试就已经可以达到契约测试的目的了;而一个生产者和多个消费者的架构下,情况就不同了,4个服务如果做集成测试,就会有 4 个集成测试,每个服务都有集成测试,每个集成测试都只关注自己业务的正确性,消费者 C 并没有提出契约修改,也没有新需求,但是突然坏掉了,消费者 C 的集成测试 一定会找到生产者修改了规约的问题,但这时契约已经被破坏了,木已成舟,为时已晚,而测试的目的则是防患于未然,希望更早的发现并解决问题;所以,如果有契约测试,就能够发挥它的作用,这也是契约测试最大的价值所在。
       在真正的业务场景下,特别是复杂的微服务集群或者时间跨度很长的系统,对某一生产者而言,修改规约时,它是无法知道存在多少消费者的,有时生产者自己也不知道有多少服务用到了它,而生产者每处的修改可能对哪些消费者的契约造成怎样的影响是很难确定的问题,这种情况修改生产者服务的 api ,需要到处通知消费者团队来做回归测试;虽然有时可能只是简单字段的修改,就像案例中那样,但每一个服务是一个团队,团队间的沟通联系可能需要好多天才能搞定;契约测试很多情况下是基于微服务而生的,但是并不代表每个微服务都需要契约测试,相对的,一些传统的单体式架构应用服务,架构设计和部署实施完全是和微服务理念相反的,但是它提供的服务也有可能被众多下游消费者使用,这样的服务其实也有很强的契约测试需求,所以,虽然契约测试是因微服务而生,但是不要把契约测试和微服务进行绑定,更多的是考虑场景,遇到单生产者,多消费者的场景,其实就可以考虑尝试使用契约测试,这种测试方式,这就是契约测试的价值所在。

四、设计契约测试
● 如何设计契约测试
   1. 常见的契约测试工具
       ■ Pact
       ■ Pacto
       ■ Sprint Cloud Contract
   2. Pact是最常见的契约测试工具
契约测试的精髓就是消费者驱动,实现消费者驱动契约测试的工具,在业界主要有 Pact Pacto Sprint Cloud Contract;这些是比较常见的工具,最为推荐的是 Pact 工具,Pact 主要支持服务间的 restful 风格的接口验证,最早使用 Ruby 语言实现,目前 Pact 扩展支持 java Js Go Swift Python Thp 等一些列语言,Github 上的Pact for python 版本开源工具链接  https://github.com/pact-foundation/pact-python
● Pact 基本工作流程
   1. 基于消费者的业务逻辑,生成契约文件
04.png       Pact的基本工作流程,就是 CDC的工作流程,首先基于消费者的业务逻辑,抛开生产者服务,模拟生产者,消费者向模拟生产者发出请求,模拟生产者响应消费者;在这个过程中,模拟生产者就是用pact实现的,对过程 2 和 3 进行记录,记录到 Pact 文件,这个文件就是契约文件,格式是 json 格式,文件里面包含的就是元数据请求和响应;工作流程的第一步,就是生成契约文件。
   2. 用消费者生成的契约对提供者进行验证
05.png第二步,这时已经抛开消费者,Pact模拟消费者,从契约文件里获取相应数据回放,向生产者发出请求,生产者响应后,和契约文件进行验证,验证响应是否正确;这个过程就是契约测试的过程,Pact就是这样工作的。

五、契约测试和接口测试以及集成测试的区别
06.png       接口测试和集成测试反映的测试纬度是不同的,针对一个项目描述测试活动时,往往会从几个方面描述;针对测试方式,可能有远程、端到端、基于AI等的,针对被测对象,可能是 Web UI 安卓应用测试,后端API测试等;针对项目被测属性,有功能性测试,性能测试,适配性测试等,看待的纬度是不同的;
● 接口测试
    1 测试方式:调用API接口
    2 被测对象:只能是API
    3 被测属性:不定。可以是被测对象的性能或安全行为,但根据上下文,默认是功能行为。
● 集成测试
    1 测试方式:不定。可以直接进行E2E的测试, 也可以进行基于Mock的测试。
    2 被测对象:不定。可以是UI, 也可以是API, 但根据上下文, 默认是API。
    3 被测属性:被测对象在与外部依赖集成时的行为表现。
● 契约测试
    1 测试方式:基于Mock的测试
    2 被测对象:消费者与生产者之间协作的schema, 也就是契约
    3 被测属性:契约满足消费者的行为表现
功能测试(这里统称接口和集成测试)只关注生产者自身,而契约测试会关注每一个消费者;功能测试的测试用例来源于生产者团队,而契约测试的测试用例基于消费者驱动,依赖于每一个消费者提供;一个生产者只会有一个功能测试,但是理论上契约测试可以有无限个,有多少个消费者就会有多少个契约测试;一个测试用例在功能测试中只会出现一次,在契约测试里却可以出现N个;同理,一个测试用例不出现在功能测试中,但可以出现在契约测试里;功能测试无需依赖其他,但是契约测试必须成对进行。

注:To be continued.. I'll come back later... >_<




标签:服务,消费者,生产者,简介,20210407,响应,测试,契约
来源: https://blog.51cto.com/15149862/2690511