我的组织大约半年前开始使用Pact来创建/验证用Java编写的REST服务/微服务之间的合同。 我们很难决定提供商测试的适当范围或把握应该是什么,并且会喜欢其他协议用户的经验。
基本上,讨论围绕提供程序测试中的mock / stub的位置进行演变。在服务中,您至少需要模拟对其他服务的外部调用,但您也可以选择模拟更接近REST资源类。
我们把它归结为两个选项:
1。第一个选项是提供者测试应该是严格的合同测试,并且只运行提供者服务的REST资源类,模拟/删除服务类/协调器等从那里使用。此合同测试将通过组件测试进行扩充,该测试将测试由提供程序测试存根/模拟的部分。
2. 第二个选项是使用提供程序测试作为组件测试,该测试将为每个请求执行整个服务组件。只有对其他组件的传递外部调用才会被模拟/存根。
这些是每个选项的专业人士的想法
选项1的专家:
选项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();
}
在上面的示例中,@ Aututired(我们使用spring)CustomerOrchestrator可以在运行提供程序测试时进行模拟,也可以注入真实的" Impl"类。如果你选择注入真正的" CustomerOrchestratorImpl.class"它会有额外的@Autowired bean依赖项,而这些依赖项反过来可能还有其他......等等。最后,依赖项将最终出现在将进行数据库调用的DAO对象或将对其他下游服务执行HTTP调用的REST客户端/组件。
如果我们采用我的"选项1"在上面的例子中我们将模拟CustomerResource中的customerOrchestrator字段,如果我们采用"选项2"我们将为CustomerResource依赖关系图中的每个依赖项注入Impl-classes(真实类),并创建模拟数据库条目并模拟下游服务。
作为旁注,我应该提到我们很少在提供商测试中实际使用真实数据库。在我们采用"选项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));
}
...
答案 0 :(得分:3)
这是一个经常出现的问题,我的回答是“为每项服务做有意义的事情”。使用pact的第一个微服务非常小而且简单,只需要测试整个服务而没有任何模拟或存根是最容易的。调用真实服务和验证测试中的调用之间的唯一区别是我们使用sqlite进行测试。当然,我们扼杀了对下游服务的呼叫。
如果设置真实数据比存根更复杂,那么我会使用存根。的然而强>!如果要执行此操作,则需要确保以与pact相同的方式验证存根调用。使用某种共享夹具,并确保对于您在pact提供程序测试中存根的每个调用,您都有一个匹配的测试,以确保行为符合您的预期。就像你将协作/合同测试链接在一起一样:
答案 1 :(得分:2)
我说选择2。
原因是因为Pact的整个存在理由是对您的代码更改有信心 - 这不会打破消费者,或者如果确实如此,就会发现一种管理变更(版本控制)的方法,同时保持交互完好。
要充满信心,你必须尽可能多地使用真实的'代码尽可能,虽然数据可以被模拟或真实,但在那一点上并不重要。请记住,您希望能够在部署之前测试尽可能多的生产代码。
我使用它的方式,我有两种类型的测试,单元测试和Pact测试。单元测试确保我的代码不会破坏愚蠢的错误或错误的输入,这对于模拟依赖关系很有用,而Pact测试用于测试消费者和提供者之间的交互以及我的代码更改没有& #39; t影响请求或数据格式。您可能会在此处模拟依赖项,但这可能会让生产中断,因为该依赖项可能会影响请求或数据。
最后,只要您使用它来测试消费者和提供者之间的合同,就可以优先考虑如何使用Pact。
答案 2 :(得分:1)
我们决定选项2.我们应该努力在提供商测试中包含尽可能多的实际代码。主要原因是,在补充组件测试中通过模拟的spring bean实现测试对称性将比使用选项2的稍微复杂的提供者测试更复杂。
感谢您的投入!