java Pact供应商测试的范围应该是什么?

u1ehiz5o  于 2023-05-05  发布在  Java
关注(0)|答案(4)|浏览(128)

大约半年前,我的组织开始使用Pact来创建/验证用Java编写的REST服务/微服务之间的契约。我们很难决定提供者测试的适当范围或把握应该是什么,并且希望从其他Pact用户的经验中获得一些输入。
基本上,讨论围绕在提供程序测试中的何处进行mock/stub。在一个服务中,你必须至少模拟对其他服务的外部调用,但你也可以选择模拟更接近REST资源类的调用。
我们把它归结为两个选择:

**1.**第一个选项是,提供者测试应该是严格的合约测试,并且只执行提供者服务的REST资源类,模拟/清除服务类/编排器等。从那里使用。这个契约测试将通过组件测试来增强,这些组件测试将测试由提供者测试存根/模拟的部分。
**2.**第二种选择是将提供者测试用作组件测试,针对每个请求执行整个服务组件。只有传递到其他组件的外部调用才会被模拟/存根。
这些都是专业人士对每个选项的想法
选项1的Pro:

  • 测试将更容易实现,并且占用的空间更小

=〉更高的隔离度。

  • 无论如何,我们可能需要其他组件测试来覆盖消费者期望中通常没有覆盖的用例(错误流等)。这样我们就不会将不同种类的组件测试(Pact和其他)混合在一个包中,使测试套件更容易理解。
    选项2的Pro:
  • 测试更多地使用了“真实的”代码=〉由于错误的模拟/存根而导致的测试错误风险更小。

我真的很有兴趣听听你的供应商测试在这方面的典型情况。是否存在最佳实践?
解释一下“组件”的含义:组件是一个微服务或更大的服务应用程序中的模块。我们采用了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依赖项,而这些依赖项又可能具有其他...最后,依赖关系将结束于将进行数据库调用的DAO对象或将对其他下游服务/组件执行HTTP调用的REST客户端。
如果我们在上面的示例中采用我的“选项1”解决方案,我们将模拟CustomerResource中的customerOrchestrator字段,如果我们采用“选项2”,我们将为CustomerResource依赖关系图中的每个依赖项注入Impl-classes(真实的的类),并创建模拟的数据库条目和模拟的下游服务。
顺便提一下,我们在提供者测试中很少使用真实的的数据库。在我们采用“选项2”的情况下,我们模拟了DAO类层,而不是模拟实际的数据库数据,以减少测试中移动部分的数量。
我们已经创建了一个“测试框架”,它可以自动模拟任何未在spring上下文中显式声明的Autowired依赖项,因此stubbing/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));

    }
    ...
afdcj2ne

afdcj2ne1#

这是一个经常出现的问题,我的答案是“做对每个服务有意义的事情”。第一个使用pact的微服务是如此的小和简单,以至于在没有任何模拟或存根的情况下测试整个服务是最简单的。对真实的服务的调用和验证测试中的调用之间的唯一区别是我们使用sqlite进行测试。当然,我们切断了对下游服务的调用。
如果设置真实的数据比存根更复杂,那么我会使用存根。然而!如果您要这样做,那么您需要确保您存根的调用以与pact相同的方式进行验证。使用某种类型的共享fixture,并确保对于您在pact提供者测试中存根的每个调用,您都有一个匹配的测试,以确保行为符合您的预期。这就像你把协作/合同测试链接在一起一样:

qcbq4gxm

qcbq4gxm2#

我建议选择2。
原因是因为Pact的整个存在理由是对您的代码更改有信心-这不会破坏消费者,或者如果它确实如此,找到一种方法来管理更改(版本控制),同时仍然保持交互完整。
为了完全自信,你必须尽可能多地使用“真实的”的代码,而数据可以是模拟的,也可以是真实的,在这一点上并不重要。请记住,您希望能够在部署生产代码之前测试尽可能多的生产代码。
我使用它的方式,我有两种类型的测试,单元测试和契约测试。单元测试确保我的代码不会因愚蠢的错误或错误的输入而中断,这对于模拟依赖关系非常有用,而Pact测试用于测试消费者和提供者之间的交互,并且我的代码更改不会影响请求或数据格式。您可以在这里模拟依赖项,但这可能会在生产中造成一些破坏,因为依赖项可能会影响请求或数据。
最后,这完全取决于您如何使用Pact的偏好,只要您使用它来测试消费者和提供者之间的契约。

nbysray5

nbysray53#

我们决定采用备选方案2。也就是说,我们应该努力在提供程序测试中包含尽可能多的真实的代码。主要原因是,在补充组件测试中从模拟的spring bean实现测试对称性比使用选项2进行稍微复杂一点的提供者测试要复杂得多。
感谢您的输入!

3gtaxfhh

3gtaxfhh4#

我们放弃了条约

根据我的经验,pact解决了一些我们没有的非常具体的问题。就像用大锤把钉子钉进墙里。

回到您的问题:

Pact基本上是尝试创建一个共享的mock-一个声明的请求和响应--这是值得信赖的 * 因为 * 它是共享的。它通过让客户端和服务使用相同的声明来解决不可信任的模拟问题。如果你进一步模拟你的服务来验证那些模拟,那么你只是回避了协议的内容。
模拟的问题是它们不值得信任。它们是不完整的,它们已经过时了,而且很难仅仅通过观察来判断它们是否正确;捕获真实的API输出是不可行的(手动捕获,自动重放)。
对于不受信任的模拟或模拟数据,典型的缓解策略是运行另一个不使用模拟的集成测试,并使用一些共享的验证代码。
所以,我想说,协议的全部意义在于选择#2。

为什么我不喜欢约定

我要指出的是,pact并不能解决所有的集成测试问题。这个问题和一些答案都是这样回答的。这看起来像是一个严格的方法来把关您的环境,并且可能表明缺乏与那些CICD质量门的纪律/标准-这里只是一个意见。
我觉得契约是卖一个解决方案,大多数不需要。

以下是我如何描述需要www.example.com的用例pact.io(必须符合所有这些)

  • HTTP Rest交互为王(忽略事件和流)
  • 相当主流的技术堆栈,具有良好的Pact语言支持
  • 您不介意定制API规范DSL -忽略jsonschema.org或OAS等。
  • 除非定期共享和验证模拟,否则它们不值得信任。
  • 快速变化的API,快速变化的消费者需求
  • API不是版本化的或向后兼容的-可能意味着客户端/服务之间的高度耦合(可能是架构问题)
  • 消费者正在推动API的变化(可能是一个丰富的UI)
  • 关于API变更的沟通/协调很困难(甚至可能功能失调):
  • API暴露了血淋淋的内脏,你的应用模型的内脏正在溢出,所以每次模型改变时,API也会改变,无论你是否愿意。
  • 你有一个组织沟通的问题,并希望解决它与高科技的依赖跟踪工具等
  • 你不介意pact控制依赖跟踪和验证;当看门人
  • 你不能信任你的开发/阶段环境--它们太不稳定,服务网格太复杂而没有容错能力,创建一个alt-stage或pre-prod环境是不可能的。
  • 您无法以有意义的方式根据阶段示例验证开发构建
  • 由于高复杂性和繁重的构建/测试周转时间,您无法创建共享集成测试。
  • 值得团队花费大量时间来理解pact、pact钩子、pact依赖跟踪、can-i-deploy和pact测试设计。值得继续为每一个新员工支付这笔费用,也值得每一个员工每6-12个月花额外的时间来研究Pact隐藏的复杂性。

好吧,所以我有点关闭的协议一般。厌倦了吗?我对那些声称XYZ技术的大多数实现失败是因为它们没有自上而下的指令来使用该技术的说法感到有点警惕。

建议替代方案

  • 如果你想验证合同,可以看看OAS/Swagger或json-schema.org合同验证(一个更标准和支持的DSL),或者看看第三方解决方案/服务,它可以模拟客户端和服务器的交互(甚至可以记录/重放)。
  • 看看“康威定律”和你的团队是如何划分的
  • 如果还不清楚,我打赌大多数需要协议的组织,实际上需要...
  • 更好的API版本控制规则,
  • API的计划生命周期,具有向后兼容的API版本
  • 阶段环境质量门或次级阶段(预生产)
  • 沟通和了解客户依赖关系
  • 跨团队通信/依赖关系跟踪是一个管理层没有管理的问题,团队没有足够的日志记录来跟踪。
  • 需要解决的架构问题(可能是由于没有架构远见的快速增长而导致的分布式巨石?)或者类似的东西……)

相关问题