消费者端使用Pact的最佳实践

Pact用于消费者与提供者的契约测试,而不是对提供者的功能测试

  • 功能测试是确保提供者在某个请求下执行正确的动作。这些测试代码属于提供者团队,不应该由消费者团队完成。
  • 而契约测试的目的是确保消费者团队和提供者团队对请求和响应达成共识。
  • Pact测试应该关注于:
    • 检查消费者如何构建请求以及处理响应时所暴露出的bug
    • 检查提供者的行为,消除理解上的偏差
  • Pact测试不应该关注于:
    • 提供者内部所暴露的bug(尽管有可能作为副产品产生)

对于提供者的功能测试和契约测试之间的区别,更多讨论见后文

关于Pact测试中测试范畴的取舍,经验法则是:如果在当前场景下不包括这部分测试内容,会引起消费者的什么bug,或者关于提供者的行为会出现怎样的理解偏差。如果答案是不会有问题,那么就不需要囊括到Pact测试的范畴中来。

使用Pact进行隔离的(单元)测试

  • mock与stub是有区别的:mock会在测试中对调用它的请求进行验证,而stub对调用它的请求不作验证。使用Pact作为stub,获取期望的响应。
  • 所谓隔离的测试(如单元测试),是指只对负责从消费者发送HTTP请求的类进行测试,而不是对整个消费者代码库进行完整的集成测试。
  • 对于消费者代码库中任何类型的功能测试或集成测试,应该谨慎对待。

为什么?

  • 如果以传统的集成测试思维使用Pact,会将自己陷入泥潭。你的消费者测试将非常脆弱,因为需要使用Pact检查每个响应路径,JSON节点,查询参数和请求头。同时,你还会发现,提供者端的待验证用例数量将会呈笛卡儿乘积式地爆炸增长。这将大大增加提供者运行验证的时间,却难以有效提升测试覆盖率。

    仔细考虑如何使用它进行非隔离测试(功能测试、集成测试)

  • 保持相互隔离的、精确匹配的验证。这将确保将域对象中的正确数据映射到请求中。
  • 对于集成测试,为了避免用例的脆弱性,尽量使用松散的、基于类型匹配(而非基于数值)的用例。同时,将灌入数据的步骤抽取出来在多个测试用例间进行共享,可以显著减少用例的数量(这将十分有用,因为Pact中的交互是以集合为单位进行验证的,这样可以消除重复)。
    如果您不关心验证消费者与提供者之间的交互,那可以使用类似Webmock的工具实现集成测试,并在这些测试和Pact测试间使用共享的夹具来构建请求/响应,确保在某种程度上确实进行了验证。

通过URL提供最新的pact契约访问地址

关于如何实现的具体方法,请参考消费者与提供者之间的契约共享

确保所有对提供者的调用都是经过Pact测试的类所发送的

不要在消费者应用中直接创建HTTP请求。应该通过客户端类(一个负责处理与“提供者”进行交互的单一职责的类)进行测试,这样会让你更有把握,你的消费者应用是按照所设想的方式来创建HTTP请求的。

确保在其他测试中使用的模型确实是由期望的响应而创建

当然,相信你已经检查过你的客户端确实是将HTTP响应反序列化为所期望的Alligator(见前文中的示例)这个模型类的对象了,但是需要确保为Alligator类写其他测试时,创建该类的对象使用的是有效的属性(例如,模型类Alligator的last_login_time字段是Time还是DateTime类型?)。一种方法是在所有测试中使用测试工厂或夹具创建模型类对象。请参阅相关信息获得更详细的解释。

当心PUT/POST/PATCH请求的无效输入、无效输出

每个交互的测试是相互隔离的,这意味着你无法先使用PUT/POST/PATCH更新资源,再使用GET获取资源,来保证发出去的数据确实被提供者所成功读取。例如,如果有一个可选的surname字段,但你却发送的是lastname字段,那么提供者很可能会忽略这个错误的字段,并返回一个200,但没有任何信号,无法告知你实际上lastname已经在/dev/null中消失得无影无踪了。

为了避免这种无效输入、无效输出的情况,可以期望响应体中包含资源的最新更新后的值,这样就可以了。

但有时候出于性能考虑,不希望在响应中包含更新的资源,另一种避免GIGO的方法是在GET响应体和PUT/POST请求体之间使用共享的测试夹具。这样就可以明确认为正在进行PUT或POST的字段就是要GET的字段。