契约测试之 - 用Pact-JS编写Message和GraphQL的契约测试

  上一篇博客介绍了如何用Pact-JS编写HTTP协议的接口的契约测试,实际,Pact-JS除了能对通过HTTP协议接口交互的服务编写契约测试外,还可以对通过发送Message进行交互的Provider和Consumer编写契约测试,还可以对通过GraphQL进行查询的服务编写契约测试。接下来先看看GraphQL的场景如何编写契约测试。

Pact-JS给GraphQL场景编写契约测试

    还是从官网的例子出发来看看如何编写,再从实现原理上理解下背后实现过程。下图中左边是provider端的代码,代码启动了一个服务,监听在4000端口,服务暴露了一个API url (/graphql),通过这个url可以查询graphql获取相关的数据。下图右边是consumer端的代码,用ApolloClient调用graphql暴露在4000端口上的服务。可以看到graphql和HTTP协议的接口请求非常类似,只是在调用查询语句上略有不同而已,本质上都是基于HTTP协议的。

   再来看看consumer端的测试,再new Pact(...)中,定义了port端口,consumer/provider名称等信息,这里的端口实际就是Pact启动mock服务的端口,这里mock的端口和真实服务是一样的端口,如果不一致,那么在consumer端,要通过环境变量提取graphql的baseUrl做替换,最终才能用mock服务替换真实的服务。中间截图部分是mock graphql的request和response,截图右边部分是consumer的真正测试部分,这里调用consumer端的query()function,验证该function的结果是否符合预期。

    provider端的测试代码和HPTT协议接口一致,这里不再介绍,可以看到,因为graphql的调用和基于http协议的接口调用很相似,所以,在写契约测试时,思路也是相同的,只是mock request的细节上有稍微区别,背后的实现原理也相同,即在consumer端运行测试时,实际是启动了一个mock服务带代替第三方,实现consumer端的测试。 

 用Pact-JS给异步交互(Message)场景编写契约测试

    除了给GraphQL调用编写契约测试外,还可以给异常场景,即通过发送消息到queue进行交互的服务编写契约测试。同样的步骤,先从官网给的样例代码开始。下面的代码左下图是provider端的代码,有一个createDog的方法来模拟创建message,message的内容如下图右边所示,中间是consumer端代码,代码中有个dogApiHandler()的方法,模拟接受、处理message(实际就是Dog对象)。可以看到官网的example代码中provider和consumer端的代码写的非常简单。

  为了后面更好的理解Pact如何实现message场景的契约测试,我们先来看一下如何要基于Queue编写provider和consumer,大致的代码应该是如何的。下面代码是使用RabbitMQ作为消息队列中间件来模拟整个流程,使用amqplib库来连接 RabbitMQ,并分别编写 Provider 和 Consumer 的代码。Provider 在连接 RabbitMQ 后将一条消息发送到名为 "message_queue" 的队列中,并打印发送的消息。Consumer 也连接 RabbitMQ,并从队列 "message_queue" 中消费消息,并在控制台上打印接收到的消息。从下面的代码可以看到,在发送或者接受消息时,需要连接队列服务地址,例如RabbitMQ服务的地址,这里假设是:amqp://localhost,连接后,发送和接受消息时通过queueName进行match的,即queueName确定了从那条queue里面发送或者接受消息。

    对比Pact-JS官网给的例子,写的比较粗糙,并没有编写较为完善的provider和consumer端代码。接着继续看consumer端和provider端的契约测试。consumer部分的契约测试,前面部分还是对期望的message做了一些模拟,然后验证的是:.verify(aynchronousBodyHandler(dogApiHandler)),这样就完成了consumer端的契约测试。对于Provider端,在new MessageProviderPact部分调用了createDog()方法,然后调用p.verify()进行了验证。从契约测试的代码来看,有点难以理解是如何完成验证的。

 从理论上讲,因为发送和接受消息也是需要RabbitMQ等的服务地址,所以,如果要实现consumer端的契约测试,显然,Pact需要mock的mq出来,这样可以往mock的mq中发送消息,用mock的mq消息的服务地址替换真实的地址,这样就可以完成Consumer端的契约测试,但从上面的契约测试脚本来看,Pact显然不是这个实现原理,那么Pact是如何实现message的契约测试的呢?来看看官网的一段解释:

Pact 将发送消息给您的消息处理程序。如果处理程序返回成功的 Promise,则消息将被保存;否则,测试将失败。需要考虑几个关键点:
Pact 实际发送的请求主体将包含在 Message 对象中,同时包含其他上下文信息,因此必须通过 content 属性获取主体内容。要测试的所有处理程序都必须具有 (m: Message) => Promise<any> 的形式 - 也就是说,它们必须接受一个 Message 并返回一个 Promise。这是我们绕过所有不同协议的方法,并且通常需要一个轻量级的适配器函数来进行转换。在这种情况下,我们使用 Pact 提供的 synchronousBodyHandler 便利函数来包装实际的 dogApiHandler,它将处理程序转换成 Promise,并提取其内容。从官网的解释来看,如果consumer端要实现契约测试,那么consumer端的所有消息处理handler必须符合上面的规则,这个handler方法返回的必须是一个Promise。这样Pact才能将需要模拟的Message替换成Handler中传入的Message,再验证Handler返回的Promise内容。从这里看,Pact编写基于Message的服务的契约测试是由前置条件的。

再来看看Provider部分,对于基于HTTP协议的接口,provider端是启动真实的服务,发送请求,查看真实的请求和response与契约中定义是否一致来完成provider端的契约测试。但是,在消息契约测试中,似乎并不是这样。对于消息,如何要进行验证,应该是在消息发送过程中,拦截下消息内容,与契约中期望的消息进行对比。在上面的例子中,只是调用createDog的方法,这个方法返回的是一个Promise,理论上Pact需要实现提取出消息的content部分出来,完成与契约文件的对比。实际是否这样呢?来看看官网的说明:

我们的 API 生产者包含一个名为 createDog 的函数,负责生成将通过某个消息队列发送给消费者的消息。我们配置 Pact 作为消息队列的替代品。这里最重要的部分是 messageProviders 块。
与 Consumer 测试类似,我们通过其 description 字段将要验证的各种交互映射到相应的处理程序(handler)。在这种情况下,请求狗的操作映射到 createDog 处理程序。请注意,这与原始的 Consumer 测试匹配。我们使用 providerWithMetadata 函数,因为我们还要验证消息的元数据(在本例中是消息将要发送到的队列)。现在,我们可以运行验证过程。Pact 将读取其 Consumer 指定的所有交互,并调用负责生成该消息的每个函数。从描述来看,provider端负责生成消息的Handler()也必须符合规则才行。总结来说,用Pact给发送message的场景编写契约测试是与前置条件的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/55857.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ(二)

二、高级特性、应用问题以及集群搭建 高级特性 1.消息的可靠性投递 在使用RabbitMQ的时候&#xff0c;作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。 rabbitMQ整个消息投递的路径为&#xff1a; produ…

如何在Visual Studio Code中用Mocha对TypeScript进行测试

目录 使用TypeScript编写测试用例 在Visual Studio Code中使用调试器在线调试代码 首先&#xff0c;本文不是一篇介绍有关TypeScript、JavaScript或其它编程语言数据结构和算法的文章。如果你正在准备一场面试&#xff0c;或者学习某一个课程&#xff0c;互联网上可以找到许多…

【啥都生】分类项目中的模型搭建代码解析

def build_model(cfg):if isinstance(cfg, list):modules [eval(cfg_.pop("type"))(**cfg_) for cfg_ in cfg]return Sequential(*modules)else:return eval(cfg.pop("type"))(**cfg)b站up啥都生维护的分类项目 这段代码的功能是完成模型搭建&#xff0c;…

Web后端基本设计思想

JavaWeb应用的后端一般基于MVC和三层架构思想实现。 MVC是一种设计模式&#xff0c;用于开发用户界面和交互式应用程序。M即Model&#xff0c;业务模型&#xff0c;负责处理应用程序的业务逻辑和数据&#xff1b;V即View&#xff0c;视图&#xff0c;负责给用户展示界面和数据&…

3ds Max建模教程:模拟布料拖拽撕裂和用剑撕裂两种效果

推荐&#xff1a; NSDT场景编辑器 助你快速搭建可二次开发的3D应用场景 1. 拖拽撕布 步骤 1 打开 3ds Max。 打开 3ds Max 步骤 2 在透视视口中创建平面。保持其长度 后座和宽度后座为 100。 创建平面 步骤 3 转到助手>假人并在 飞机的两侧。 助手>假人 步骤 4 选…

基础实验篇 | PX4控制器的外部通信

PX4控制器的外部通信 01 实验名称及目的 PX4控制器的外部通信&#xff1a;在进行硬件在环仿真时&#xff0c;我们常常需要向设计的Simulink控制器中发送数据&#xff08;传感器数据、故障触发、控制指令、参数调整等&#xff09;&#xff0c;同时接收一些感兴趣的数据。RflySi…

ELK 企业级日志分析系统(一)

目录 一、ELK 简介 1.1 组件说明 1.2 为什么要使用ELK 1.3 完整日志系统的基本特征 1.4 ELK工作原理 二、Elasticsearch的介绍 2.1 Elasticsearch的核心: 三、Logstash 3.1 Logstash简介 四、Kibana 五、部署ELK日志分析系统 5.1 服务器配置 5.2 ELK Elasticse…

在PHP8中检测数据类型-PHP8知识详解

在PHP 8中&#xff0c;可以使用多种方法来检测数据类型。以下是常用的四种方法&#xff1a;使用 gettype() 函数、使用 is_* 系列函数、使用 get_debug_type() 函数、使用 get_class() 函数。 一、使用 gettype() 函数 gettype() 函数返回给定变量的数据类型。例如&#xff1a…

机器学习-New Optimization

机器学习(New Optimization) 前言&#xff1a; 学习资料 videopptblog 下面的PPT里面有一些符号错误&#xff0c;但是我还是按照PPT的内容编写公式&#xff0c;自己知道符号表示什么含义就好了 New Optimization 机器学习(New Optimization)NotationOn-line VS Off-line常用优…

Html5播放器按钮在移动端变小的问题解决方法

Html5播放器按钮在移动端变小的问题解决方法 用手机浏览器打开酷播云视频&#xff0c;有时会出现播放器按钮太小的情况&#xff0c;此时只需在<head>中加入下面这段代码即可解决&#xff1a; <meta name"viewport" content"widthdevice-width, initia…

Python入门二

目录&#xff1a; python封装与property装饰器python继承与类型检查python多态与superpython 模块与包错误与异常Debug 调试与分析python类型注解python数据类dataclasspython内置装饰器python装饰器学生信息管理系统 1.python封装与property装饰器 封装的概念 封装&#x…

如何压缩高清PDF文件大小?将PDF文件压缩到最小的三个方法

PDF格式是一种非常常用的文档格式&#xff0c;但是有时候我们需要将PDF文件压缩为更小的大小以便于传输和存储。在本文中&#xff0c;我们将介绍三种PDF压缩的方法&#xff0c;包括在线PDF压缩、利用软件PDF压缩以及使用WPS缩小pdf。 首先&#xff0c;在线PDF压缩是最常用的方…

《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 1. 解决的问题 利用块和线程索引&#xff0c;从全局内存中访问指定的数据。 2. 分析 通常情况下&#xff0c;矩阵是用行优先的方法在全局内存中线性存储的。如下。 8列6行矩阵&#xff08;nx,ny&#xff09;&#xff08;…

JVM内存结构

JVM内存结构 5个部分 程序计数器 Java 虚拟机栈 本地方法栈 堆 方法区 JDK 1.8 同 JDK 1.7 比&#xff0c;最大的差别就是&#xff1a;元数据区取代了永久代。元空间的本质和永久代类似&#xff0c;都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于…

获取 NGINX QUIC+HTTP/3 预览版的二进制包

原文作者&#xff1a;Robert Haynes of F5 原文链接&#xff1a;获取 NGINX QUICHTTP/3 预览版的二进制包 转载来源&#xff1a;NGINX 官方网站 我们很高兴宣布&#xff0c;NGINX QUICHTTP/3 预览版现提供以下两个发行版的预构建二进制包&#xff1a; Red Hat Enterprise Linux…

java实现5种不同的验证码图片,包括中文、算式等,并返回前端

导入以下依赖 <!--图片验证码--><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency> 编写controller package com.anXin.user.controlle…

牛客网Verilog刷题——VL42

牛客网Verilog刷题——VL42 题目答案 题目 请设计一个可以实现任意小数分频的时钟分频器&#xff0c;比如说8.7分频的时钟信号&#xff0c;注意rst为低电平复位。提示&#xff1a;其实本质上是一个简单的数学问题&#xff0c;即如何使用最小公倍数得到时钟周期的分别频比。设小…

级联选择框

文章目录 实现级联选择框效果图实现前端工具版本添加依赖main.js导入依赖级联选择框样式 后端数据库设计 实现级联选择框 效果图 实现 前端 工具版本 node.js v16.6.0vue3 级联选择框使用 Element-Plus 实现 添加依赖 在 package.json 添加依赖&#xff0c;并 npm i 导入…

高通滤波器,低通滤波器

1.高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。 import cv2 import numpy as np from scipy import ndimagekernel_3_3 np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]]) print(kernel_3_3) kernel_5_5 np.array([[-1,-1,-1,-1,-1],[-1,1,2,1,-1],[-1,2,4,2,-…

iOS——锁与死锁问题

iOS中的锁 什么是锁锁的分类互斥锁1. synchronized2. NSLock3. pthread 递归锁1. NSRecursiveLock2. pthread 信号量Semaphore1. dispatch_semaphore_t2. pthread 条件锁1. NSCodition2. NSCoditionLock3. POSIX Conditions 分布式锁NSDistributedLock 读写锁1. dispatch_barri…