契约提供者测试的范围应该是什么?

Joe*_*son 9 java testing unit-testing pact pact-java

大约半年前,我的组织开始使用Pact来创建/验证用Java编写的REST服务/微服务之间的合同.我们很难决定提供商测试的适当范围或把握应该是什么,并且会喜欢其他协议用户的经验.

基本上,讨论围绕提供程序测试中的mock/stub的位置进行演变.在服务中,您必须至少模拟对其他服务的外部调用,但您也可以选择模拟更接近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();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

我们创建了一个"测试框架",可以自动模拟任何未在spring上下文中显式声明的Autowired依赖项,因此stubing/mocking对我们来说是一个轻量级的过程.这是运行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));

    }
    ...
Run Code Online (Sandbox Code Playgroud)

Bet*_*rie 5

这是一个经常出现的问题,我的回答是“为每项服务做有意义的事情”。使用pact 的第一个微服务非常小而简单,最容易测试整个服务而无需任何模拟或存根。调用真实服务和调用验证测试之间的唯一区别是我们使用 sqlite 进行测试。当然,我们截断了对下游服务的调用。

如果设置真实数据比存根更复杂,那么我会使用存根。然而!如果您打算这样做,那么您需要确保您存根的调用以与协议相同的方式进行验证。使用某种共享的fixture,并确保对于您在pact provider 测试中存根的每个调用,您都有一个匹配的测试,以确保行为符合您的预期。就像您将协作/合同测试链接在一起,如下所示: 在此处输入图片说明