前言
本文源于一次关于处理契约测试在CI上更新的小讨论。仅抛砖引玉,期待更多地讨论和建议。
上下文铺垫
契约测试多用于微服务架构,当不同服务之间需要互调API时,传统测试无法覆盖,需要通过制定一个类似通信协议(这里叫契约)来确保内部服务正常通信。契约测试会测试外部服务的边界,以查看服务调用的输入输出,并测试该服务能否符合契约预期:作为数据的生产者,需要确保其提供的数据能够符合消费者的要求。作为数据的消费者,需要确保从生产者获取数据后,能够有效地被处理。
- 角色:消费者(Consumer) & 生产者(Provider)
- 思想:Consumer Driven (需求驱动)
- 具体实现:Consumer 端提供一个类似“契约”的东西(如json 文件,约定好request和response)交给Provider 端,告诉Provider 有什么需求,然后Provider 根据这份“契约”去实现。
为了更好地理解,我们来举个栗子。
小明的妈妈让小明去超市帮她买东西,同时她列了一份购物清单给小明。小明拿着这份购物清单去超市把清单上的东西都买了回来给妈妈。
标重点
购物清单就是小明和妈妈之间的契约,消费端是小明的妈妈,她提供了契约,生产端是小明,他根据这份契约去实现。
事情是这样的
A:小二,上图!记得加五毛钱特效啊!
B:图来喽!
简单标下重点
:A ,B两个服务,独立在CI部署,A对B有依赖。现在CI上A的版本是1.0, B是2.0,远端有一个中央仓库存储了A,B之间最近成功版本的契约。对于A,B的每次版本升级,在跑CT的时候都要去仓库取上一个版本对应的契约跑测试,保证该次变化对于上个版本的需求是兼容的。
A,B一直相安无事,突然有一天,程序员小衰接到一个需求,要改B服务的某个API(该API被A服务消费,A,B之间已经存在相互约束的契约),需求很简单,天真烂漫地小衰在本地契约测试顺利跑过之后,顺手提交了B服务的CI。挖了个去,瞬间CI变成了酱紫。
小衰有点紧张,心中默念提交十四字法则:新加先提生产端,删除先提消费端。于是被故事选中地悲催小衰眉头一皱,发现不管A,B间的提交顺序,CI到底都是要挂的,因为这道题超纲了!
由于A,B已有的契约约束,当A需要B更新其API时,是先提交A的契约,还是更改B的功能到最新版本?如果只是单纯字段的增删,提交十四字法则是可以保障的。比如,A做为消费端,需要增加一个字段,这时候我们先提交B,新版本B从远端仓库拿到旧的契约,发现还是满足之前的约定,新版本的B被更新到远端仓库。我们再提交新版本的A,A从远端仓库拿到新版本的B,一经验证是我想要的,A再被更新到远端仓库。
但是小衰就没有这么幸运了,因为测试基础设施的限制,同时由于契约测试粒度过细,A,B之间的契约约定了某数组结构具体的数据值,其数据值的更改,导致契约测试就过不了。最后在现有中央仓库约束的机制下为了让CI过,还得在生产端保证旧的API不变,创建一个新的API,先提交,然后在消费端更新契约文件并把原来API的调用指向生产端的新API,最后生产端还得把旧API的更新为新API,消费端再把调用指回旧的API。显然是多了很多成本的。
那么问题来了
显然小衰遇到的问题比较特殊,但由此引发了思考,我们契约测试到底该测些什么,它的测试粒度怎么取舍?
现在的模式是消费端A通过Pact把mock数据变成一份json契约文档,生产端B把json传入测试,通过构建数据来验证契约是否满足,验证过程要求每个字段的值必须匹配。但实际上这些数据也只是消费端mock的假数据,对于我生产端,还有必要挨个字段值验证嘛,还是说我只需要验证每个字段的类型是消费端想要的就好了,具体什么值我也根本不关心?
我个人的理解是业务逻辑在单元测试完全可以覆盖,契约测试更倾向测服务间的协议地连通,我消费端提供了一份我需要的数据的结构,那我生产端验证能够造出这个结构的数据即可,具体业务,不该由我来承担,职责很清晰。
当然纯属个人见解,接触契约测试几个月,可能理解上存在一些偏差。