下文来自我在公司内部分享的 pact 实践。主要是面向开发人员的分享。目前听到的反馈还不错,期望能带给更多人启发。
需要说明的是,文中我基于官方 demo 为我们现有项目写了 pact tests 作为演示,这里就不便公开了,只能贴一些执行结果截图,大家感受一下。看完还是去啃官方文档吧 :)
========我是正文分割线=========
接口开发过程的协作问题
众所周知,目前的 Payment 系统存在多个后台模块,相互间的 API 调用非常多。如下图,往往模块 A,B,C 是由不同的开发人员维护的,使用的语言也不一样,而现在的测试方式,主要是依赖测试人员通过最源头的入口 Endpoint 进行测试,如果发现了问题,再通过一层层的日志,发现到底是哪个模块出了问题。这类测试是当然必不可少,但是把问题都集中在测试阶段发现,这样的反馈周期显然不够快。
其实,在开发接口的过程中,开发童鞋需要关注两类问题:模块内部逻辑,以及上下游接口的变更。
模块内部逻辑的测试,要提倡开发童鞋写 Unit Tests,这里不再赘述;而上下游接口的变更,过去我们会使用 Mock 工具。但使用一般的 Mock 工具还会面临以下问题:
-
一个 URL,不能对应多个 Response。比如查询 User Balance 接口,上下游模块之间要约定好“查询成功”和“失败”等多个情况下的返回格式是怎样的。
-
Mock 信息的版本管理。比如,谁有权限更新 Mock Server 配置的接口信息?如何将 Mock Server 的接口配置信息与开发模块的版本对应起来?
-
上下游通知机制。张三将变更情况告知了李四,李四可能忙忘记了,忘记改代码了;也可能在沟通过程中,接口变更细节并没有真正理解到位,上下游双方的认知不一致。
针对上述上下游模块交互的情况,业界推出了 “契约测试”(Contract Tests)的概念,pact 正是用于契约测试的。https://docs.pact.io/
Pact 使用场景
Pact 工作的核心,是 Consumer Driven Contracts testing。本质上也是一种 Mock 服务,但是比一般的 Mock 框架考虑得更为全面。
针对上述的三类问题,Pact 是这样解决的:
-
Provider 一个接口可以设置多个 Provider states ( Response),通过在 Provider 注入数据,来匹配 Consumer 响应。
-
Pact tests 跟开发代码在一个项目中。可以在本地生成接口相应的 json 文件(也就是上下游之间的契约),方便与代码一起管理,也可以上传到 Pact Broker 中,通过 Web 页面来查看管理。
-
Pact tests 很方便做持续集成。也就是说,可以把 pact tests 的运行配置到 Jenkins Job 中,Consumer 和 Provider 项目有变更都需要跑 pact tests,如果失败了会自动通知相关人员。
每个工具都有自己的适用场景,我们不能期望一个工具来解决所有问题。
适用场景
-
消费者( Consumer )和 提供者( Provider )均是由同一组织提供。所以,Ping++ 和支付渠道或第三方服务之间的接口就别考虑 pact 啦~
-
Provider 提供的功能,往往是因为 Consumer 的需求来推动的。
-
一个 Provider 的 Consumer 数量不多,Provider 知道有哪些 Consumer 模块会消费。
以下场景中,Pact 难以发挥作用
-
Provider 和 Consumer 团队之间难以进行良好的沟通。
-
测试新功能,或现有的 Provider 功能不会根据 Consumer 的需求变更所调整。比如第三方服务的 API,OAuth Provider。
-
Provider 逻辑的正确性测试。功能逻辑的自测可以通过 Provider 自身的 Unit Tests 来进行,pact 不太适合。
-
性能和压力测试。
-
“pass through” 接口测试。如果 Provider 只是将请求内容传递给下游服务(比如 消息队列),上下游之间并不关心 body(可以是任意内容)的话,也不用考虑 pact
Pact 工作原理
如下图(图片来自官方文档),简单来说,pact 的工作分为两步:
-
Consumer 向 pact 发请求。pact 会根据响应,生成 json 文件(也就是 Contract)。这个步骤的完成,就意味着 Consumer 的需求很明确了。
-
pact 向 Provider 发请求,将会以 json 文件中的内容作为期望结果,与 Provider 返回的结果进行匹配验证。
至于 Consumer,Provider 和 pact 的交互,就是我们要在代码中定义的。后文将以模块 AAA 和 BBB 为例进行说明。
Go 模块使用 pact 示例
AAA 模块会调用 BBB 模块的接口,查询某一个 App 下某个 User 的余额信息。
1. 准备工作
下载 pact-go ,运行 pact-go daemon
https://github.com/pact-foundation/pact-go#installation
2. Consumer ( 模块 AAA )
下图源自:https://www.codeproject.com/Articles/1180045/Consumer-Driven-Contract-Testing-with-PactNet-Csha
2.1 编写 tests
(在此略过)
2.2 执行 tests
应用了 patch 之后,在 tests 目录下执行命令:go test -v .
2.3 查看生成的 json 文件
可以很清楚地看到对请求和响应地定义,同时设置了匹配规则( matchingRules ) 为 type。
目前支持的 matchers 参见: https://github.com/pact-foundation/pact-specification/tree/version-4
目前 pactSpecification 版本是 2.0.0
3. Provider( 模块 BBB)
下图源自:https://www.codeproject.com/Articles/1180045/Consumer-Driven-Contract-Testing-with-PactNet-Csha
3.1 获取 Contract
目前没有搭建 Pact Broker,可以把上面生成的 json 文件拷贝到 BBB 项目中。
3.2 编写 tests(在此略过)
3.3 执行 tests
在 tests 目录下执行命令:go test -v .