• FAQ
    • Pact适用于什么场景?
    • Pact不适用于什么场景?
    • 为什么Pact不使用JSON Schema?
    • 为什么Pact使用具体的JSON文档而不使用更灵活的JSONPaths?
    • 为什么不支持指定可选属性?
    • 为什么契约是生成的而不是静态的?
    • 如何测试最新的开发环境版本和生产环境版本的服务消费者端API?
    • PACT代表什么?
    • Pact为什么不是测试公共API的最佳工具?
    • 为什么Pact不是测试“透传”API的最佳工具?
    • 我还需要端到端测试吗?
    • 如何处理版本控制问题?
    • 当服务消费者和服务提供者不在同一团队时,该如何使用Pact
    • 如何防止当契约非法时服务消费者去部署。
    • 如何测试OAuth和请求中其他安全报头?
    • 如何测试响应中的二进制文件,例如下载功能?

    FAQ

    Pact适用于什么场景?

    当你(或你的团队/组织/合作组织)同时负责开发服务消费者与服务提供者,并且服务消费者的需求被用来驱动服务提供者的功能时,Pact对于在服务集成方面进行设计和测试是最具价值的。它是组织内部开发和测试微服务的绝佳工具。

    Pact不适用于什么场景?

    • 性能和压力测试。
    • 服务提供者的功能测试——这是服务提供者自己的测试应该做的。Pact是用来检查请求和响应的内容及格式。
    • 当你在必须使用实际测试的API才能将数据载入服务提供者的情况下(例如:公共API)。想一想为什么?
    • “透传”API的测试,是指服务提供者仅将请求内容传递到下游服务而不做任何验证。想一想为什么?

    为什么Pact不使用JSON Schema?

    不论你是否定义一个schema,你始终需要一个模拟服务器返回的响应的具体示例,和一个服务提供者端能够重复执行的请求的具体示例。假如使用schema的话,那么你的代码必须依照schema来生成一个示例,而生成的这些值对测试并没有太大帮助,而且也起不到可读性强、有意义的文档的作用。假如同时使用schema和一个具体示例,那你就做了重复的工作,因为schema几乎可以从一个示例中看出来。定义更灵活匹配的功能(如”任意长度数组””)在v1版本中暂无,将在v2版本中支持(beta版已经发布, 参见v2版灵活匹配).

    为什么Pact使用具体的JSON文档而不使用更灵活的JSONPaths?

    Pact是由一个使用具有读/写RESTful接口的微服务的团队开发。当在读取JSON文档时,灵活的JSONPaths是有用的,但是在创建具体的JSON文档示例用来向一个服务发送POST或PUT请求时,JSONPaths就不那么有利了.

    为什么不支持指定可选属性?

    首先,Pact假设当你在做测试验证时,你能够控制服务提供者(及服务消费者)的数据。如果不是这样的话,那么Pact也许并非适合你的最佳工具.

    其次,考虑以下场景,假如Pact支持断言某个属性$.body.name可能存在于响应中,而你在消费者端中也写了用于处理可选属性$.body.name的代码,但实际上服务提供者返回的是$.body.firstname,没有任何测试会失败,来告诉你你做了错误的假设。请记住,服务提供者可以返回额外数据而不破坏契约,但它必须至少返回你在契约中期望的数据。

    为什么契约是生成的而不是静态的?

    • 可维护性:Pact是“以示例为契约”,示例可能涉及大量的JSON。手工维护JSON文件既费时又容易出错。 通过动态创建契约文件,你可以选择在fixture文件中保存的期望,或者从你的领域对象来生成它们(这是推荐的方法,因为这种方法能确保你的领域对象与契约文件中的JSON永远保持同步)。

    • 服务提供者状态:在模拟服务器上动态设置期望让你能够使用服务提供者的状态,这意味着你可以在不同的测试中发出同一个请求,而得到不同的预期响应。 这确保你能够正确测试服务消费者端的所有代码路径(例如,处理不同的响应码或资源的不同状态)。 如果所有的交互都是在启动时从静态文件加载的,那么模拟服务器就不知道要返回哪个响应。 以这个gist为例。

    如何测试最新的开发环境版本和生产环境版本的服务消费者端API?

    参见这篇文章。

    PACT代表什么?

    它不代表任何东西。 它只是“pact”这个单词,是契约(contract)的另外一种说法。 Google将“pact”定义为“个人或各方之间的正式协议”。这给出了一个极好的总结。

    Pact为什么不是测试公共API的最佳工具?

    契约中定义的每个交互都应该被独立验证,不依赖于上一次交互的上下文。 依赖之前测试结果的测试是脆弱的,而且会将你重新陷入到集成测试的泥潭中,而这正是想通过使用契约测试来极力摆脱的困境。

    那么该怎样测试依赖于服务提供者数据的请求呢?提供者状态允许你在交互发生前就可以向数据源注入数据,从而预置服务提供者端数据,这样就可以生成与消费者期望相匹配的响应。提供者状态也允许消费者对同一请求测试不同的期望响应(例如不同的响应码,或者同一资源的不同数据子集)。

    假如你用Pact来测试公共API,设置合适提供者状态的唯一方法就是使用你所测试的API,与其他”正常”的Pact测试相比,这种测试将更耗时,而且更脆弱。

    如果除集成测试外,你认为依然需要测试公共API,那就使用像VCR之类的其他工具吧!

    为什么Pact不是测试“透传”API的最佳工具?

    在契约验证时,Pact并不会去测试请求的执行在服务提供者端造成的副作用,它只是检查服务返回的响应与预期的响应是否匹配。如果你的API只是传递一个消息至下游系统(如消息队列)而不对内容做任何的验证,那么你就可以在请求中发送任何东西,而服务提供者将始终返回按同样的方式返回。你真正需要测试的契约是在服务消费者和下游系统之间。仅仅检查提供者是否返回200(OK)并不能使你有信心相信消费者与下游系统之间能够正确工作。

    对于这种API来说,你真正需要的是服务消费者与下游系统间的一个非HTTP契约,你可以参考这个gist,了解对于非HTTP通信的测试来说,如何使用Pact来生成契约和匹配代码。

    我还需要端到端测试吗?

    简而言之:需要

    这个问题的答案取决于你所在组织的风险应对状况。在你对系统无缺陷的信心和你发现bug之后的响应速度之间,通常有一个权衡。进行10小时的测试可能使你对系统正常工作更有信心,但是当bug不可避免地被发现时,它会降低你快速推出新版本的能力。

    如果你工作在一个优先考虑”敏捷性”超过”稳定性”的环境中,那么将那些用在维护端到端测试上的时间花在改进生产环境的监控上会更为值得。

    如果你工作在一个更传统的”大爆炸式发布”的环境中,那么聚焦于系统所提供的核心业务功能,做一些精选的小批量端到端测试,会让你更有信心去做发布。同时考虑使用“语义监控”(Semantic monitoring,一种“生产环境测试”)作为替代。

    如何处理版本控制问题?

    消费者驱动的契约在某种程度上让你不需要去做版本控制。只要所有的契约测试都通过,那你就可以部署这些修改而无需对API做版本控制。如果对于服务提供者有大的更改,那么你可以分为几步来处理:将新的字段/端口添加至服务提供者并部署它。更新消费者端并部署,让它使用新的字段/端口。从服务提供者端移除旧的字段/接口并部署。在此过程中的每一步中,所有的契约测试都应该通过。

    使用Pact Broker,你可以在发布服务消费者时标记来生产环境契约文件的版本。这样,你在服务提供者端做的任何修改,都将会对生产环境契约文件做验证,同时会对最新的契约文件做验证,以保证向后兼容。

    如果你需要同时支持服务提供者的多个版本的API,你可能会在HTTP Header中指明服务消费者将使用哪个版本,或者使用另外一个URL。由于这实际上是不同的请求,因此这些交互可以在同一个契约中验证,而没有任何问题。

    当服务消费者和服务提供者不在同一团队时,该如何使用Pact

    Pact是消费者驱动的契约,而不是”独裁者驱动的契约”。不能因为它是”消费者驱动”,服务消费者团队就只写一个契约文件后把它扔给服务提供者团队而不做任何沟通。契约应该作为团队协作的第一步。

    Pact的工作方式是,即使服务消费者本身是错误的,假如消费者期望的内容与服务提供者返回的不一致,也会导致契约验证失败。这有点不幸,但它就是这么干的。

    将契约验证从服务提供者的其他测试中分离出来,单独运行在一个持续集成(CI)流水线中,是个不错的方式。否则如果跟其他测试混在同一个CI流水线中,则有人会对此产生疑虑,因为另外一个团队可能破坏服务提供者的CI流水线。

    让消费者团队及时知道契约验证失败是非常重要的,因为这意味着此时不能部署消费者端。假如服务消费者团队与服务提供者团队使用的是不同的CI实例,那你需要考虑当契约验证失败时如何与消费者团队及时沟通。你应该做出以下行动之一:

    • 在流水线上做配置,当契约验证失败时,发邮件通知服务消费者团队。
    • 更好的方式是,假如可能的话,在消费者的CI上运行一份提供者的构建,只需要运行单元测试和契约验证。这样的话,当契约验证失败时,消费者团队也能察觉到,让消费者始终关心契约是否验证通过。

    使用拥有最新契约文件的URL来做契约验证。不要依赖于手工处理(比如,有些团队将契约文件拷贝至提供者服务),因为手动拷贝很难保证正确性,你的验证任务可能会给你一个错误的肯定。不要试图通过手动更新契约文件的方式来”保护”你的构建不失败。契约验证恰如你在集成过程中的金丝雀,手动更新契约文件相当于给你的金丝雀一个防毒面具。

    如何防止当契约非法时服务消费者去部署。

    使用 Pact Broker Webhooks

    使用Pact Broker上的webhooks,当修改后的契约被提交后,立即触发服务提供者端的构建。

    确保服务提供者的验证结果被发布回Broker

    自Pact Broker 2.0以上版本,及Pact的Ruby实现1.11.1以上版本,服务提供者的验证结果可被发布回代理(broker)并显示在索引页面上。服务消费者小组应该在部署之前在查询索引页上的验证结果。

    需要注意的一点是,只有服务提供者的生成环境版本被验证后,部署服务消费者才是安全的。

    其他可供考虑的方法有:

    协作

    那么,对于初学者,你必须与提供商团队密切协作!

    有效使用代码分支

    当然,在服务消费者可以安全发布之前,服务消费者对契约的新假设进行验证是非常重要的。在分支被合并到主干之前,需要先在服务提供者端进行验证。

    使用版本控制来检测被修改的契约:

    如果你同时将主契约文件提交至代码库中,可以让你的CI构建有条件地执行——如果契约发生了修改,你需要等待服务提供者端构建成功,服务契约未发生修改,则可安全部署。

    如何测试OAuth和请求中其他安全报头?

    对于像OAuth2这种由标准定义并由实施该标准的库实现的交互,我们建议不要在这些场景下使用Pact。标准是定义良好的,不会频繁更改的,你可能会有更简单的测试选项可用(或许是你使用的框架提供的功能)。

    对于使用这些报头的API,情况会有点复杂,尤其是在实际需要验证合法口令的服务提供者端。想想为什么?

    当Pact读取契约文件在服务提供者端进行验证时,它需要有一个合法的口令,如果该口令已经存储在Pact文件中,那么它也可能已过期。

    以下是一些可选的方法

    • 创建在测试期间使用的模拟认证服务——这使得你有最好的控制权。

    • 当在使用JVM时,则可以使用请求过滤器在发送给提供者之前修改请求报头。

    • 在服务提供者端配置一个轻松的OAuth2验证服务,只要报头匹配规范(例如Authorization报头),就可以接收有效报头。你可以利用提供者状态特性实现此功能。

    • 使用Ruby的Timecop或类似的库来操作运行时时钟。

    注意:任何将请求发送给运行的提供者之前来修改它的的选项都会增加你失去交互关键部分的可能性,从而使你面临风险。谨慎使用。

    请参阅以下链接进一步讨论:

    • https://github.com/realestate-com-au/pact/issues/49#issuecomment-65346357

    • https://groups.google.com/forum/#!searchin/pact-support/oauth%7Csort:relevance/pact-support/zTnDlOgdYhU/tq_Yx8MnIgAJ

    • https://groups.google.com/forum/#!topic/pact-support/tSyKZMxsECk

    • http://stackoverflow.com/questions/40777493/how-do-i-verify-pacts-against-an-api-that-requires-an-auth-token/40794800?noredirect=1#comment69346814_40794800

    如何测试响应中的二进制文件,例如下载功能?

    我们建议验证交互中的核心方面——例如请求本身以及响应头。

    1. {
    2. state: 'I have a picture that can be downloaded',
    3. uponReceiving: 'a request to download some-file',
    4. withRequest: {
    5. method: 'GET',
    6. path: '/download/somefile'
    7. },
    8. willRespondWith: {
    9. status: 200,
    10. headers:
    11. {
    12. 'Content-disposition': 'attachment; filename=some-file.jpg'
    13. }
    14. }
    15. }