大约半年前,我的组织开始使用 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”,我们将为 CustomerResource 中的每个依赖项注入 Impl 类(真正的类)依赖图并创建模拟数据库条目和模拟下游服务。
作为旁注,我应该提到我们很少在提供者测试中实际使用真实的数据库。在我们采用“选项 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));
}
...