背景在现代开发模式中,基于微服务的开发模式越来越普遍,但是随着项目规模的扩大,服务之间的依赖关系越来越紧密。当不同的开发团队开发不同的服务时,服务提供者的改变会影响到很多消费它的消费者。为了保证系统的正确性和一致性,这将需要大量的沟通成本和代码修改的时间成本。之前遇到的一个客户,服务之间的依赖太多,物理依赖各种,再加上各种其他的原因,导致集成测试的时候bug激增。对于他们来说,集成测试需要依赖各个服务版本和真实物理环境的一致性,因此他们的集成测试通常需要几个小时才能完成,这大大降低了整体效率。另外,集成测试中发现的问题也会让他们需要很长时间才能定位问题。在平时的开发过程中经常会遇到类似的问题。由于依赖方接口变更,系统集成时错误频发,需要修改整体代码,延缓了开发进度。.为了解决此类问题,契约测试应运而生。契约测试并不是什么新鲜事物,但是在实际的项目体验中,发现使用好的契约测试确实可以大大提升开发效率,所以写这篇文章来简单总结一下契约测试的一些内容。首先,什么是合同测试?契约测试是一种确保两个独立的系统或微服务兼容并且可以相互通信的方法。合约测试有两种,一种是服务商驱动,一种是消费。通过驱动。如下图所示,左边是服务消费者,右边是服务提供者。消费者调用提供者的接口并消费数据的交互过程会被记录为一个契约,其中包括提供服务的提供者和消费者是谁,以及消费者对服务提供者的期望(比如服务的参数)请求并返回结果)。服务提供者会根据这个契约反复验证自己是否能够满足消费者的需求,这就是所谓的消费者驱动。合约测试主要是验证服务层提供的数据是否可以被消费者正常使用。它不深入测试服务的行为,而只专注于测试服务的输入和输出。因此,与繁重的集成测试相比,Contract测试会更轻、更快。契约测试在形式上类似于API层面的UT,但本质上仍然是集成测试,比API测试处于金字塔顶端,容易增加数量和不稳定合同测试。契约测试是如何实践的接下来我们将从代码和流水线设计两个方面来讲解具体的契约测试实践:代码层面:为了完成契约测试,我们可以使用一个叫做pact的工具。Pact是用于支持契约测试的代码优先工具。目前支持java、python、go等主流开发语言。Pact中的一些基本概念:Contract:合约文件,在Pact中也叫pact,可以保存在本地,也可以保存在broker中Provider:实际运行的生产者服务Consumer:在pact中接收producer发送的数据和provider分别做不同的事情:Consumer端:consumer端会做以下事情:首先使用pactdsl定义它消费的接口的request和response,并注册到mockserver中然后在consumer上测试side将向由pact启动的本地模拟服务器发送真实请求。然后pact将实际请求与预期请求进行比较。如果它们一致,则返回预期的响应。最后由消费者确认返回值是否正确。以上步骤都是正确的。通过后,整个consumertest的pacttest才算结束。此时,消费者设置的合约会发布到一个叫pactbroker的地方,统一管理合约。Pactbroker是pact提供的统一管理合约的服务。在这个服务中,开发者可以清楚的看到所有服务提供者和消费者的详细信息。一般来说,cosumer端的主要功能是生成合约(文件的载体)。验证请求和响应的工作是可选的。通过消费者端的集成测试,确保生成的合约确实是消费者真正期望的。换句话说,就是“测试测试测试”。Provider端:在provider端,pact会模拟一个consumer,向provider端真正运行的进程发送请求。provider收到request后,会根据自己的code返回真实的response给pact,然后pact会把这个response和之前在pactbroker上得到的consumer定义的contract进行比较,如果provider能满足合同,验证通过。当消费者和提供者测试都通过后,产品就可以部署到指定的环境中了。以上是消费者驱动的实用方法。消费者驱动的契约测试主要适用于以下场景:消费者和提供者都是可控的。消费者需求变化可以成为供应商需求。消费者数量不多,由于提供者能够管理并满足上述条件,更适合使用消费者驱动的契约测试。在消费者驱动的上下文中,服务提供者可以根据消费者提出的契约快速响应。然而,实际情况可能并没有那么好。我之前遇到的一些客户的内部情况与上述场景不符。他们的产品极度依赖一些外部的底层依赖,底层依赖变化频繁,导致他们在集成测试的时候经常发现底层变化了。在这种场景下,提供者驱动的契约测试更为合适。服务提供者同意合同,然后许多消费者满足合同。当提供者发生变化时,消费者可以及时感知并快速反馈。整个实践过程只需要将上面的consumer和provider的操作进行转置即可。换句话说,消费者驱动和提供者驱动之间的区别在于谁响应合同变更。上文提到,当外部提供者依赖不可控时,提供者驱动的模型会更合适,而消费者驱动的模型则相反。流水线设计在选择消费者驱动的契约测试策略时,作为消费者,它要做的就是发布契约,告诉提供者它的需求。那么,作为提供者,需要检查自己的实现是否能够满足消费者的需求。那么当它的实现不能满足合约时,此时应该在pipeline合约测试阶段显示fail,并通知相应的provider让其快速修正。如图所示,当消费者发布新版本的合约时,这将导致提供者端的管道失效,然后提供者将知道他们需要根据新合约修改实现。与消费者驱动设计相反,提供者驱动设计是,当提供者发布新合约时,消费者端的管道会变红,直到消费者根据新合约修改自己的代码后,才能进入后续的集成测试..契约测试的好处(1)测试速度快,不需要依赖多个系统之间的交互。细心的同学通过上面的描述会发现,服务依赖方法在合约测试时并不需要真正调用。合约测试通过mock依赖模拟依赖方的行为,大大提高了测试速度。(2)可以并行开发。由于mock的存在,服务的消费者和提供者可以按照预先定义的契约并行开发。(3)发现问题后,可以快速定位问题:因为问题只会出现在当前测试的服务或组件中,甚至可以准确知道是哪个API测试失败了(4)确认合约后,开发者可以进行测试无需将代码推送到远端即可在本地执行(5)测试转发将原本只能通过集成测试验证的工作,转为单元测试和接口测试,以更轻便的方式快速验证,更早的发现问题使得后续测试更快。合同测试与其他测试的比较。一般来说,契约测试是单元测试和集成测试之间的一个阶段。它侧重于比单元测试更细的粒度,但它不能取代集成测试。尤其是当你的产品对环境的依赖性极强的时候,集成测试仍然是必不可少的一环。契约测试的存在只是为了让你在开发过程中的联调更快,集成时出现的问题更少。
