将契约用于具有一系列请求和响应的服务

Using pact for a service with a sequence of requests and responses

这是我的用例:

我的服务是:ServiceA。
它依赖于以下服务:ServicesB 和 ServiceC。

ServiceA 向 ServiceB 发送一个带有一些身份验证详细信息(用户名+密码)的 POST 请求,ServiceB 回复一个具有 sessionId 的 json 文档。

要求:

POST /authenticate
{
    "username": "_at_api",
    "password": "xxx"
}

回复:

{
    "sessionId": "axy235da7ad5a24abeb3e7fbb85d0ef45f"
}

以上 sessionId 用于所有 api 从 ServiceA 到 ServiceC 的调用。

ServiceA 向 serviceC 请求使用 POST 请求和 serviceC returns 以及作业 ID(字母数字)开始作业。

要求:

POST /jobs/local/start
Header: Authentication: axy235da7ad5a24abeb3e7fbb85d0ef45f
{
    ...
}

回复:

{
    "status": "RUNNING",
    "jobId": "a209016e3fdf4425ea6e5846b8a46564abzt"
}

ServiceA 使用上面返回的 jobId 不断轮询 serviceC 以完成作业:

要求:

GET /jobs/status/a209016e3fdf4425ea6e5846b8a46564abzt
Header: Authentication: axy235da7ad5a24abeb3e7fbb85d0ef45f

回复:

{
    "status": "RUNNING"
}

轮询一直持续到状态返回为完成或失败。

回复:

{
    "status": "COMPLETED"
}

如何使用 Pact 来测试 serviceA?

我的计划是只使用单元测试和合同测试来实现90%以上的代码覆盖率。这是个好主意,还是我需要使用虚拟服务器进行单独测试?我的理解是,Pact 是虚拟服务器的超集(例如:mountebank),虚拟服务器能做的一切,Pact 都能做。所以我不需要单独的组件测试。另外,看起来合同测试完全取代了端到端测试,所以我也不需要端到端测试。这样对吗?

Also, it looks like Contract testing completely replaces end-to-end testing, so I do not need end-to-end testing as well. Is this right?

没有。合同测试不是功能测试(参见 this excellent article, with the same title

什么是合同测试?

契约测试是关于测试两个组件是否能够通信。

考虑房子和 postal 工人之间的合同:postal 工人需要知道他们可以接近房子并交付 post(而且,有时他们可能无法做到这一点——也许邮箱已满)。

从post所有工人的角度来看,合同如下所示:

  • 找到 postbox(包含成功和失败案例)
  • 将 post 投递到 post 盒子(包含成功和失败案例)

请注意,postal worker 对 postbox 的实现一无所知。也许有多种原因导致 post 箱子的递送可能会失败 - 也许门被卡住了,也许箱子已满,也许 post 太大放不下。

在这个假设的案例中,我们的 postal 工人在这些情况下没有做任何不同的事情 - 他们只是无法交付。因此,从合同的角度来看,失败的原因是无关紧要的。合同 - 工人可以尝试交付 post,他们可以成功或不成功 - 可以在不列举所有可能的失败原因的情况下进行测试。

请参阅文章 linked above 以获取更详细的示例,但要引用它的末尾:

Contracts should be about catching:

  • bugs in the consumer
  • misunderstanding from the consumer about end-points or payload
  • breaking changes by the provider on end-points or payload

Pact 的一个非常好的功能是您可以仅针对它们所依赖的通信位来测试多个合约。

请注意,消费者契约测试仅描述消费者需要进行或理解的沟通。合同不是(必然)完整的 API 描述。

好的,但为什么不能我使用合同测试进行端到端测试?

可以使用像 Pact 这样的工具来代替端到端测试。然而,尽管契约测试与端到端测试所需的功能有很多相似之处,但契约测试(尤其是 Pact)并不是为端到端测试而设计的。

如果您通过扩展现有消费者的测试来进行端到端测试(例如,将失败的所有可能原因添加到 post工人的测试),那么就不再清楚契约手段。合同现在描述了通信如何与行为一起工作。

当您开始添加更多消费者(例如,包裹快递员)时,这会导致问题 - 您是复制所有消费者中的所有失败案例,还是只将它们保留在原始消费者测试中?如果你重复测试,那么如果你改变了提供者的行为,你就会有很多事情要改变——而且你的测试会很脆弱。如果您不复制测试,那么您的端到端测试就会卡在一个消费者中——如果您停用该消费者,就会面临丢失测试的所有问题。

使用纯合同测试,如果您要添加更多消费者已经理解的可能失败原因,您(理想情况下)无需更改任何内容。

如果您尝试此操作,还有许多其他原因会让您头疼(您的测试开始严重依赖于精确数据,如果您的测试结束,验证失败和 can-i-deploy 挂钩的含义将会改变-到端测试),但关键是 Pact 并不是设计来替代端到端测试的。您可以那样使用它,但这是不可取的,并且可能会导致痛苦的维护。

如何使用 Pact 来测试 serviceA?

您分别描述每个请求,使用 Pact provider state 作为每个请求的先决条件。

此外,您可能会发现 上的问题很有帮助。

虽然来晚了,但对于遇到此问题的每个人来说:

契约规范 (v3.0) 仅支持每次交互的单个请求(但每个消费者-提供者对的多个交互)。在提供商方面,每次交互都会导致单独的测试。因此,虽然在提供者端有一个序列或请求 运行 在概念上不是一个好主意,但它在技术上也行不通。

虽然在消费者方面,您有一个替代方案 - 我鼓励不要滥用端到端测试。在我的例子中,我有一个 class 实现了一个 template/strategy 模式,它涉及多个请求并且不允许在消费者端注入中间状态。它要求所有请求都具有有效响应。

在这种情况下,pact-jvm-consumer-junit (=JUnit4) 允许在单个测试方法(在我的例子中是 Kotlin)上指定多个 PactVerification 注释:


@Test
@PactVerifications(
    value = [
        PactVerification("Service B", fragment = "authenticateOk"),
        PactVerification("Service C", fragment = "jobStartOk")
        PactVerification("Service C", fragment = "jobStatusOk")
    ]
)
fun `test successful job execution`() {
 // add your test here.
}

在提供者方面,上面的所有片段都作为单独的测试执行,因此它们需要上面答案中指定的正确状态。

我在 JUnit4 中得到了这种方法 运行。不过还没有找到一种快速模仿它的方法 JUnit5。