【问题标题】:What should be the scope of a Pact provider test?Pact 提供者测试的范围应该是什么?
【发布时间】:2017-04-20 10:56:06
【问题描述】:

大约半年前,我的组织开始使用 Pact 创建/验证用 Java 编写的 REST 服务/微服务之间的合同。 我们很难决定提供商测试的适当范围或掌握程度,并且希望从其他协议用户的经验中获得一些输入。

基本上,讨论围绕在提供程序测试中模拟/存根的位置展开。在服务中,您至少必须模拟对其他服务的外部调用,但您也可以选择模拟更接近 REST 资源类。

我们将其归结为两个选项:

1. 第一个选项是提供者测试应该是严格的契约测试,并且只执行提供者服务的 REST 资源类,模拟/存根从那里使用的服务类/编排器等.此合同测试将增加组件测试,这些组件测试将测试由提供者测试存根/模拟的部分。

2. 第二种选择是使用提供者测试作为组件测试,为每个请求执行整个服务组件。只有对其他组件的传递性外部调用才会被模拟/存根。

这些是专业人士对每个选项的想法

选项 1 的专业人士:

  • 测试将更易于实施并且占用空间更小
    => 更高的隔离度。
  • 我们可能无论如何都需要其他组件测试来涵盖通常未包含在消费者的用例中的用例 期望(错误流等)。这样我们就不会混合不同的 多种组件测试(Pact 和其他)集于一身,使测试套件更易于理解。

选项 2 的专业人士:

  • 测试执行更多“真实”代码 => 测试风险更低 由于不良模拟/存根导致的错误。

我真的很想听听您的提供商测试在这方面的典型表现。有最佳做法吗?

澄清我们所说的“组件”的含义: 组件是大型服务应用程序中的微服务或模块。我们从 Martin Fowlers http://martinfowler.com/articles/microservice-testing/ 中获取了“组件”的定义。

提供者服务/组件通常在 Jersey 资源类中有一个 REST 端点。此端点是 Pact 提供者测试的提供者端点。一个例子:

@Path("/customer")
public class CustomerResource {

    @Autowired private CustomerOrchestrator customerOrchestrator;

    @GET
    @Path("/{customerId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response get(@PathParam("customerId") String id) {
        CustomerId customerId = CustomerIdValidator.validate(id);
        return Response.ok(toJson(customerOrchestrator.getCustomer(customerId))).build();
    }

在上面的示例中,@Autowired(我们使用 spring)CustomerOrchestrator 可以在运行提供程序测试时被模拟,或者您可以注入真正的“Impl”类。如果您选择注入真正的“CustomerOrchestratorImpl.class”,它将具有额外的 @Autowired bean 依赖项,而这些依赖项又可能具有其他...一个 REST 客户端,它将对其他下游服务/组件执行 HTTP 调用。

如果我们在上面的示例中采用我的“选项 1”解决方案,我们将模拟 CustomerResource 中的 customerOrchestrator 字段,如果我们采用“选项 2”,我们将为每个依赖项注入 Impl 类(真正的类)在 CustomerResource 依赖图中创建模拟数据库条目和模拟下游服务。

作为旁注,我应该提到,我们很少在提供程序测试中实际使用真正的数据库。在我们采用“选项 2”的情况下,我们模拟了 DAO 类层,而不是模拟实际的数据库数据,以减少测试中移动部件的数量。

我们创建了一个“测试框架”,它自动模拟任何未在 spring 上下文中显式声明的 Autowired 依赖项,因此存根/模拟对我们来说是一个轻量级的过程。这是一个提供者测试的摘录,该测试执行 CustomerResource 并启动存根的 CustomerOrchestrator bean:

@RunWith(PactRunner.class)
@Provider("customer-rest-api")
@PactCachedLoader(CustomerProviderContractTest.class)
public class CustomerProviderContractTest {

    @ClassRule
    public static PactJerseyWebbAppDescriptorRule webAppRule = buildWebAppDescriptorRule();

    @Rule
    public PactJerseyTestRule jersyTestRule = new PactJerseyTestRule(webAppRule.appDescriptor);

    @TestTarget public final Target target = new HttpTarget(jersyTestRule.port);

    private static PactJerseyWebbAppDescriptorRule buildWebAppDescriptorRule() {
        return PactJerseyWebbAppDescriptorRule.Builder.getBuilder()
            .withContextConfigLocation("classpath:applicationContext-test.xml")
            .withRestResourceClazzes(CustomerResource.class)
            .withPackages("api.rest.customer")
            .build();
    }

    @State("that customer with id 1111111 exists")
    public void state1() throws Exception {
        CustomerOrchestrator customerOrchestratorStub = SpringApplicationContext.getBean(CustomerOrchestrator.class)
       when(customerOrchestratorStub.getCustomer(eq("1111111"))).thenReturn(createMockedCustomer("1111111));

    }
    ...

【问题讨论】:

  • 嗨 Joel,很高兴听到您一直在使用 Pact。您能否详细说明“组件”的含义,因为这不是我们用于 Pact 的术语。此外,如果您可以提供这两个选项的代码示例,那么很容易理解您的意思。干杯。
  • 您好,组件是微服务或大型服务应用程序中的模块。我们从 Martin Fowlers martinfowler.com/articles/microservice-testing 中获取了“组件”的定义。

标签: java unit-testing testing pact pact-java


【解决方案1】:

这是一个经常出现的问题,我的回答是“做对每项服务有意义的事情”。使用 pact 的第一个微服务是如此的小而简单,以至于在没有任何模拟或存根的情况下测试整个服务是最简单的。调用真实服务和验证测试调用之间的唯一区别是我们使用 sqlite 进行测试。当然,我们将对下游服务的调用存根。

如果设置真实数据比存根更复杂,那么我会使用存根。 但是!如果您要这样做,那么您需要确保您存根的调用以与协议相同的方式进行验证。使用某种共享固定装置,并确保对于您在协议提供者测试中存根的每个调用,您都有一个匹配的测试来确保行为符合您的预期。就像您将协作/合同测试链接在一起一样:

【讨论】:

  • 感谢 Beth,在存根时链接方面的优点。我想我所追求的是我们应该如何确定测试范围的一些“规则”,以便我们可以为所有开发人员创建一个通用的“测试策略/最佳实践”。我们有点担心,如果我们允许每个人在每种情况下做他们认为最合适的事情,我们很快就会有很多不同风格的提供者测试。强调链接方面可能是确保我们不会在测试网络中出现任何漏洞的一种方法,即使我们允许在哪里灵活地存根。
【解决方案2】:

我说选择选项 2。

原因是因为 Pact 的整个存在理由是对您的代码更改充满信心 - 这不会破坏消费者,或者如果它这样做,找到一种管理方法更改(版本控制),同时仍保持交互不变。

要完全自信,您必须尽可能多地使用“真实”代码,虽然数据可以是模拟的或真实的,但这并不重要。请记住,您希望能够在部署之前尽可能多地测试生产代码。

我使用它的方式,我有 2 种类型的测试,单元测试和 Pact 测试。单元测试确保我的代码不会因愚蠢的错误或错误的输入而中断,这非常适合模拟依赖项,而 Pact 测试用于测试消费者和提供者之间的交互,并且我的代码更改不会影响请求或数据格式。您可能会在此处模拟依赖项,但这可能会导致生产中断,因为该依赖项可能会影响请求或数据。

最后,这完全取决于您如何使用 Pact,只要您使用它来测试消费者和提供者之间的合同。

【讨论】:

    【解决方案3】:

    我们已经决定了选项 2。也就是说,我们应该努力在提供程序测试中包含尽可能多的真实代码。主要原因是,在补充组件测试中从模拟的 spring bean 中实现测试对称性比使用选项 2 进行稍微复杂的提供程序测试要复杂得多。

    感谢您的意见!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-06
      • 1970-01-01
      • 1970-01-01
      • 2022-06-15
      • 1970-01-01
      相关资源
      最近更新 更多