这种微妙的沟通问题在有多个团队参与的时候经常发生。令人高兴的是,有一个很好的缓解措施:消费者驱动的合约测试。在 IDE 没有帮助我们进行类型检查的系统中,我们需要测试我们的集成,但我们希望尽量减少全面的集成测试。
集成测试很重,运行成本很高,而且本身就是耦合的。如果我们已经投资开发了微服务,我们不想在测试时倒退并制造一个大的集成单体。那么,我们如何让自己得到信心,让我们确信我们正在建立一个真正有效的东西呢?
数据模拟(Mock)是一种常见的解决方案,但数据模拟本身也有一个问题。为了建立数据模拟,生产团队和消费团队在开发之初就会就接口的情况进行对话。他们达成了一个协议,然后消费团队就去尝试写一个数据模拟,这个模拟看起来就像他们对生产团队所说的代码的理解。
在理想的情况下,他们会做得很好。问题是,消费团队经常会把自己的假设也写进了模拟中,而他们也许不是知道其他代码是什么样子的,是否是合适这部分模拟的,毕竟不是消费团队编写的代码。
在顺利的情况下,他们得到了正确的结果。单元测试全部通过,而且在集成阶段也继续通过,一切都很好。不幸的是,这并不总是发生。有时,
实际的实现与消费团队所理解的不同
,要么是因为生产团队改变了他们的想法,要么是因为某个地方的人做了一个不正确的假设。
在这种情况下,测试仍然会通过。然而,当我们真正整合真实的服务时,它就会失败。问题是,模拟的行为没有经过真实服务的验证。生产团队很可能甚至从未见过已经创建的模拟。
一个更好的选择是有一个消费者驱动的合约测试。合约测试的美妙之处,以及为什么它与模拟不同,是双方都与合约测试互动。对于消费者来说,合约测试就像一个方便的模拟。
在另一方面,合约测试对于生产团队也是一个方便的功能测试。它是一个更深刻的验证,而不仅仅是像 OpenAPI 的语法检查。合约测试实际上也会检查语义和行为,这节省了生产团队编写功能测试的时间。
如果所有的东西都是兼容的并且工作的,所有的合约测试都会通过。这是一个快速的信心提升,因为它们运行起来成本很低、也很轻便。如果生产团队破坏了什么,他们的测试将失败,并提供早期警报,在破坏性变化逃逸到集成环境之前。如果 API 发生变化,新版本的合约就会被双方(或连接的中间人)提出。
现在有几个不同的合约测试系统。如果你在 Spring 的生态系统中,Spring Cloud Contract 工作得非常好。如果你是一个多面手,那么我非常喜欢 Pact。它有几乎所有你可能使用的语言的绑定。