跳转到主要内容
Chinese, Simplified

随着向微服务转移,或μ服务更时尚,使用Pact进行消费者驱动的合同测试,这是一种策略,可以确定我们所有的服务是否能够正确地相互通信。不要求我们所有的服务都相互通信。它还使我们能够确定任何服务的消费者是谁。这很有帮助,因为如果你知道你的消费者是谁,那么当你想破坏某些东西时,你就知道该和谁交谈。或者当他们破坏某些东西时

我们为基于HTTP的微服务做了这个。然后我们添加了一些消息队列和lambda函数。只是因为我们可以。

通过消息队列进行异步通信


我们开发的一些服务开始以异步方式通过消息队列进行通信。很明显,现在知道消息的消费者是谁更为重要。使用同步通信,如果出现故障,参与者是谁是明显的。使用邮件队列,您可能不知道谁正在使用您的邮件,或者是否由于邮件更改而导致任何失败。

我觉得,典型的态度是生成消息,将其扔到队列中,这不再是你的问题。看起来非常类似于我们尝试使用Restful API和微服务进行更改的想法。

我目睹了由于消息中格式错误的日期导致两个团队之间的服务系统故障。通过多次部署到QA环境,需要数天才能解决。

关于如何确保服务通过消息队列进行通信的问题没有受到格式错误的消息的影响,让我想到了联系测试。进行合同测试以确保消息生产者和消费者能够正确地进行通信似乎是正确的想法。但问题比基于HTTP的服务更复杂,我们可以在HTTP级别模拟事物。一切都讲HTTP。说到消息队列,根据使用的消息队列,协议会有所不同。而且我不想创建一个模拟ActiveMQ,RabbitMQ,SQS,Kafka和Kinesis服务器,仅举几例。

这导致认识到,为了验证合同,底层消息队列通常是无关紧要的。只要您可以确保消息生成器生成格式正确的消息,并且使用者能够使用它,您就不需要实际的消息队列。这让我很开心。

 

所以我创建了消息合约。

这与常规Pact测试的工作方式类似。 我们创建一个消费者测试,生成并发布一个pact文件,然后验证我们的消息提供者是否生成了正确的消息。

消费者测试


大多数消息队列使用者是使用回调机制实现的,通常是某种消息监听器,它通过与SDK库集成从消息队列接收消息。为了能够测试我的消费者,我将使用者分成两个类,一个与消息队列库集成的消息监听器,以及一个处理消息的消息处理程序。除了将消息传递给处理程序之外,消息侦听器不执行任何操作。通过这种方式,我可以只使用不需要消息队列的处理程序编写消费者合同测试。

消费者合同测试以类似的方式工作。我指定了我希望收到的消息,然后获得Pact测试框架以使用每条消息调用消息处理程序。到目前为止,这已经用于测试Kafka,SQS和Kinesis消息消费者。

下面是一个JVM消息处理程序的示例,它将处理来自Kafka主题的消息作为Spock测试。但是你应该能够使用任何测试框架,比如JUnit或TestNG。

第1步 - 定义消息期望


消费者测试从定义消息期望开始。基本上,我们正在设置我们希望收到的信息。我们使用PactMessageBuilder来设置协议和消息。

given:
def messageStream = new PactMessageBuilder().call {
    serviceConsumer 'messageConsumer'
    hasPactWith 'messageProducer'

    given 'order with id 10000004 exists'

    expectsToReceive 'an order confirmation message'
    withMetaData(type: 'OrderConfirmed') // Can define any key-value pairs here
    withContent(contentType: 'application/json') {
        type 'OrderConfirmed'
        audit {
            userCode string('messageService')
        }
        origin string('message-service')
        referenceId regexp('\\d+\\-\\d', '10000004-2')
        timeSent timestamp
        value {
            orderId regexp('\\d+', '10000004')
            value decimal(10.00)
            fee decimal(10.00)
            gst decimal(15.00)
        }
    }
}

 

第2步 - 使用生成的消息调用消息处理程序


此示例测试从Kafka主题获取消息的消息处理程序。 在这种情况下,Pact消息被包装为Kafka MessageAndMetadata类。

when:
messageStream.run { Message message ->
    messageHandler.handleMessage(new MessageAndMetadata('topic', 1,
        new kafka.message.Message(message.contentsAsBytes()), 0, null, valueDecoder))
}

 

类似地,对于处理程序从SQS接收消息的示例,该消息是由像Jackson这样的库反序列化的对象,您可以执行以下操作:

when:
messageStream.run { Message message ->
  def order = objectMapper.readValue(message.contentsAsBytes(), Order)
  assert messageHandler.handleMessage(order) != null
}

 

这里的objectMapper是Jackson库中的ObjectMapper。 以与收到实际消息时相同的方式对消息进行反序列化非常重要。

第3步 - 验证消息是否已正确处理


我们的处理程序应该接收消息并将订单详细信息保存到我们的订单存储库中,因此我们可以检查它是否正常工作。

then:
def order = orderRepository.getOrder('10000004')
assert order.status == 'confirmed'
assert order.value == 10.0

 

Pact在这里没有做任何事情,它只是将DSL的期望转换为JSON文档,并将其传递给一些要执行的代码。实质上,它假装是一个消息队列。如果一切都通过,则生成pact文件。这是重要的一点,因为我们现在可以发布它。

验证我们的消息提供商


关闭循环的下一步是验证提供程序服务是否正确生成消息。我们通过让Pact以与消费者测试相反的方式工作来实现这一目标。同样,Pact将伪装成消息队列并让消息提供者向其发送消息。这将与已发布的pact文件进行匹配。

我们通过从消息生成代码中分割消息发送代码来实现此目的。这样,负责将消息放入消息队列的类委托给另一个类来生成实际消息。然后我们可以调用后一类在验证测试期间生成一条消息,以确定它是否正确生成消息。唯一需要注意的是,以这种方式测试的类必须是实际用于生成真实消息的类。

Pact-JVM使用Java注释来查找可以调用消息生成器并返回生成的消息的测试类。如果您使用的是JUnit,则可以使用AmqpTarget来驱动此行为,而不是用于常规pact验证测试的HttpTarget。另请参阅在Gradle中验证消息提供程序和在Maven中验证消息提供程序,以获取有关如何在这些工具中启用此功能的信息。

现在,当我们验证消息pact文件时,Pact-JVM将在测试类路径中查找使用@PactVerifyProvider注释的方法,这些方法与pact文件中的内容具有匹配的描述。在我们之前的示例中,它是订单确认消息。

因此,这是一个验证来自先前的消费者测试的消息的示例。

class ConfirmationKafkaMessageBuilderTest {

  @PactVerifyProvider('an order confirmation message')
  String verifyMessageForOrder() {
      Order order = new Order()
      order.setId(10000004)
      order.setExchange('ASX')
      order.setSecurityCode('CBA')
      order.setPrice(BigDecimal.TEN)
      order.setUnits(15)
      order.setGst(15.0)
      order.setFees(BigDecimal.TEN)

      def message = new ConfirmationKafkaMessageBuilder()
              .withOrder(order)
              .build()

      JsonOutput.toJson(message)
  }

}

在这种情况下,ConfirmationKafkaMessageBuilder是用于生成发送到Kafka主题的消息的类。但是这可以用于任何消息队列,因为我们没有使用任何特定的Kafka。 Pact-JVM现在将调用verifyMessageForOrder方法并验证返回的内容是否与pact文件中的消息内容匹配。

综上所述


我们可以使用联系测试之类的策略来验证通过消息队列进行通信的使用者和提供者是否可以工作而无需实际运行的消息队列。我们通过以下步骤执行此操作:

  1. 将消息使用者拆分为特定于队列的消息处理程序和处理该消息的类。
  2. 编写一个调用消息处理类的消费者测试。
  3. 发布生成的pact文件。
  4. 将我们的消息提供程序拆分为特定于队列的发布者和消息生成器
  5. 设置我们的验证(使用Pact-JVM)来调用使用@PactVerifyProvider注释的测试类的方法
  6. 获取带注释的测试方法以从消息生成器返回消息JSON。

现在我们可以在部署之前捕获这些日期格式问题。

在本博客的第二部分中,我将向您展示如何使用相同的技术来验证与AWS lambda函数的合同。

 

原文:https://dius.com.au/2017/09/22/contract-testing-serverless-and-asynchronous-applications/

本文:

讨论:请加入只是星球或者小红圈【首席架构师圈】

Article
知识星球
 
微信公众号
 
视频号