微服务契约测试框架-Pact
契约测试
契约测试的思想就是将原本的 Consumer 与 Provider 间同步的集成测试,通过契约进行解耦,变成 Consumer 与 Provider 端两个各自独立的、异步的单元测试。
契约测试的优点:
契约测试与单元测试以及其它测试之间没有重复,它是单纯验证Provider与Consumer之间按预期的方式交互,定位准确;不需要部署真实的系统环境、Mock机制、没有真实API调用,运行非常快、反馈及时、修复周期短、成本低,在这种情况下,自动化测试流水线运行更快了,产品流水线出产品安装包也更快。因此,显然契约测试才是真正对的选择。
契约测试的缺点:
- 契约测试无法做安全或性能测试等。
- 契约测试采用Mock机制,所以没有集成测试更接近真实环境,也不能给业务人员做验收,可视性差。
- 契约测试基于不同的服务使用的协议不同,验证契约的复杂度会不同,复杂度过高时,需要权衡是否有必要加契约测试。
别再加端到端集成测试了,快换契约测试吧 - 简书 (jianshu.com)
基于Consumer驱动的契约测试分两个阶段:
- Consumer生成契约,开发者在Consumer端写测试时Mock掉Provider,运行测试生成契约文件;
- Provider验证契约,开发者拿契约文件直接在Provider端运行测试进行验证。
- 契约测试实践篇
Pact和Spring Cloud Contracts是目前最常用的契约测试框架, Pact 实现就采用 Consumer-driven Contract Testing
Pact
Overview | Pact Docs
Pact 是事实上的 API 合约测试工具。用快速、可靠且易于调试的单元测试取代昂贵且脆弱的端到端集成测试。
- ⚡ 闪电般的速度
- 🎈 轻松的全栈集成测试 - 从前端到后端
- 🔌 支持 HTTP/REST 和事件驱动系统
- 🛠️ 可配置的模拟服务器
- 😌 强大的匹配规则可防止测试变脆
- 🤝 与 Pact Broker / PactFlow 集成,实现强大的 CI/CD 工作流程
- 🔡 支持12+种语言
为什么使用契约?
使用 Pact 进行合同测试可让您:
- ⚡ 本地测试
- 🚀 部署速度更快
- ⬇️ 缩短变更提前期
- 💰 降低 API 集成测试的成本
- 💥 防止中断性更改
- 🔎 了解您的系统使用情况
- 📃 免费记录您的 API
- 🗄 无需复杂的数据夹具
- 🤷 ♂️ 减少对复杂测试环境的依赖
克隆项目
pact有不同语言的版本,这里用的js语言
git clone https://github.com/pact-foundation/pact-js.git
消费者测试
Consumer Tests | Pact Docs
针对消费者完成单元测试,使用测试替身mock server,使单元测试可以通过,并生成契约文件。
(主要是定义契约文件)
运行单个示例
- 切换到所需的示例文件夹
cd examples/v3/typescript
- 安装所有示例依赖项
npm install
- 运行所有示例 -
npm run test
运行后成功显示通过一条用例
问题:运行npm run test提示Cannot find module '@pact-foundation/pact' or its corresponding type declarations.
解决办法:在pact-js目录下执行npm install @pact-foundation/pact,然后再运行npm run test
运行后会生成pacts目录
该目录下生成的是契约文件
消费者是User Web,生产者是User API
{"consumer": {"name": "User Web"},"interactions": [{"description": "a request to get a user","providerStates": [{"name": "a user with ID 1 exists"}],"request": {"method": "GET","path": "/users/1"},"response": {"body": {"age": 25,"id": 1,"name": "东方不败","province": "河南"},"headers": {"content-type": "application/json"},"matchingRules": {"body": {"$": {"combine": "AND","matchers": [{"match": "type"}]}},"header": {}},"status": 200}}],"metadata": {"pact-js": {"version": "12.1.0"},"pactRust": {"ffi": "0.4.0","models": "1.0.4"},"pactSpecification": {"version": "3.0.0"}},"provider": {"name": "User API"}
}
源码
index.ts文件
import axios, { AxiosPromise } from 'axios';export class UserService { //export关键字表示将该类导出为一个模块的公共接口,使其能够在其他模块中被引用和使用。constructor(private url: string) {}public getUser = (id: number): AxiosPromise => {return axios.request({baseURL: this.url,headers: { Accept: 'application/json' },method: 'GET',url: `/users/${id}`,});};
}
user.spec.ts
import * as chai from 'chai';
import * as chaiAsPromised from 'chai-as-promised';
import * as path from 'path';
import * as sinonChai from 'sinon-chai';
import { PactV3, MatchersV3, LogLevel } from '@pact-foundation/pact';
import { UserService } from '../index';
const { like } = MatchersV3;
const LOG_LEVEL = process.env.LOG_LEVEL || 'TRACE';const expect = chai.expect;chai.use(sinonChai);
chai.use(chaiAsPromised);describe('The Users API', () => {let userService: UserService; //声明了一个变量userService并指定了它的类型为UserService。// 创建两个应用之间的契约const provider = new PactV3({ //pact提供的类consumer: 'User Web',provider: 'User API',logLevel: LOG_LEVEL as LogLevel,});const userExample = { id: 1, name: '东方不败',age:25,province:"河南" }; //契约const EXPECTED_BODY = like(userExample);// 定义测试套件describe('get /users/:id', () => {it('returns the requested user', () => {// 定义测试用例1 一个it是一个测试用例// provider.given('a user with ID 1 exists').uponReceiving('a request to get a user').withRequest({ //请求信息method: 'GET',path: '/users/1',}).willRespondWith({ //响应信息status: 200,headers: { 'content-type': 'application/json' },body: EXPECTED_BODY,});return provider.executeTest(async (mockserver) => { //执行测试// ActuserService = new UserService(mockserver.url); //模拟了一个urlconst response = await userService.getUser(1); //获取到response// 校验response的data与契约相同expect(response.data).to.deep.eq(userExample);});});});
});
原理
1、消费者使用pact提供的mock完成单元测试
2、pact把交互写入契约文件(通常是一个json文档)
3、消费者将契约分布给中间人(或者分享出去)
4、pact收到契约,并使用本地运行的provider重放请求
5、提供者在契约测试中需要去除依赖,来确保测试更快速和确定。
在pact-js的doc目录可以看到用户手册。
Consumer API有很多属性:
| `new PactV3(options)` | 为proverder API创建mock server test替身
| `addInteraction(...)` | `V3Interaction` | 注册交互
| `given(...)` | `ProviderStateV3` | 交互中提供者的状态 |
| `uponReceiving(...)` | string | 场景名称,在契约文件中given和uponReceiving必须唯一。
| `withRequest(...)` | `V3Request` | The HTTP 请求信息
| `willRespondWith(...)` | `V3Response` | The HTTP响应信息 |
| `executeTest(...)` | - |执行用户定义函数,如果执行成功,会更新契约文件。
new PactV3的构造参数:
| Parameter | Required? | Type | Description
| ------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------
| `consumer` | yes | string | 消费者名称
| `provider` | yes | string | 生产者名称
| `port` | no | number | 运行mock服务的端口,默认是随机
| `host` | no | string | 运行mock服务的地址, defaults to 127.0.0.1
| `tls` | no | boolean | 系诶一 (default false, HTTP)
| `dir` | no | string | 契约文件输出目录
| `log` | no | string | 日志文件
| `logLevel` | no | string | 日志级别Log level: one of 'trace', 'debug', 'info', 'error', 'fatal' or 'warn'
| `spec` | no | number | Pact的版本 (defaults to 2)
| `cors` | no | boolean |允许跨域,默认是false
| `timeout` | no | number | The time to wait for the mock server tq5o start up in milliseconds. Defaults to 30 seconds (30000)
第一步是为Consumer API创建一个test
例子采用的是Mocha框架
1)创建契约项目
2)启动Mock provider来替代真正的Provider
3 ) 添加消费者期望结果
4)完成test
5) 验证Consumer和Mock service之间产生的交互(即运行代码,看是否pass)
6)产生契约文件 (代码运行完就会产生契约文件)
生产者测试
Matching | Pact Docs
一个provider测试的输入是一个或者多个契约文件,Pact验证provider符合这些契约文件。
在简单的示例下,可以使用本地契约文件验证provider,但在实际使用时,应该使用Pact Broker来管理契约或者CI/CD工作流
1、启动本地的Provider服务
2、可选的,检测 API 以配置提供程序状态
3、运行provider验证步骤
一旦为消费者创建了契约,就应该用Provider来验证这些契约。Pact提供了如下API。
Verification Options:
参数 | 是否必须 | 类型 | 描述 |
providerBaseUrl | TRUE | string | provider的基础url |
pactBrokerUrl | false | string | pact broker的base url |
provider | false | string | provider的name |
consumerVersionSelectors | false | ConsumerVersionSelector|array | pe配置验证的版本 |
consumerVersionTags | false | string|array | 使用标签取出最新的契约 |
providerVersionTags | FALSE | string|array | 应用到provider的标签 |
providerVersionBranch | FALSE | string | 分支 |
includeWipPactsSince | FALSE | string | |
pactUrls | FALSE | array | 本地契约文件路径数组或者基于HTTP的url,如果不用Pact Broker则该项必须 |
providerStatesSetupUrl | FALSE | string | 该参数已废弃 |
stateHandlers | FALSE | object | |
requestFilter | FALSE | function (Express middleware) | 改变请求或者输出 |
beforeEach | FALSE | function | 在每一个交互验证前执行的函数 |
afterEach | FALSE | function | 在每一个交互验证后执行的函数 |
pactBrokerUsername | FALSE | string | Pact Broker的验证username |
pactBrokerPassword | FALSE | string | Pact Broker的验证password |
pactBrokerToken | FALSE | string | Pact Broker的验证token |
publishVerificationResult | FALSE | boolean | 发布验证结果至Broker,只有在持续集成时才设置这个参数 |
providerVersion | FALSE | string | provider的版本 |
enablePending | FALSE | boolean | 挂起契约 |
timeout | FALSE | number | 超时时间,默认是30秒 |
logLevel | FALSE | string | 不需要,log级别在环境变量中设置 |
最好将契约验证测试作为单元测试套件的一部分,因为可以很方便的使用stubbing,lac或者其他工作。
const { Verifier } = require('@pact-foundation/pact');// (1) Start provider locally. Be sure to stub out any external dependencies
server.listen(8081, () => {importData();console.log('Animal Profile Service listening on http://localhost:8081');
});// (2) Verify that the provider meets all consumer expectations
describe('Pact Verification', () => {it('validates the expectations of Matching Service', () => {let token = 'INVALID TOKEN';return new Verifier({providerBaseUrl: 'http://localhost:8081', // <- location of your running providerpactUrls: [ path.resolve(process.cwd(), "./pacts/SomeConsumer-SomeProvider.json") ],}).verifyProvider().then(() => {console.log('Pact Verification Complete!');});});
});
匹配规则
可以使用正则表达式或者基于对象类型匹配或者数组来验证相应的结构
匹配规则取决于契约文件
相关文章:

微服务契约测试框架-Pact
契约测试 契约测试的思想就是将原本的 Consumer 与 Provider 间同步的集成测试,通过契约进行解耦,变成 Consumer 与 Provider 端两个各自独立的、异步的单元测试。 契约测试的优点: 契约测试与单元测试以及其它测试之间没有重复,…...

LightGlue论文翻译
LightGlue:光速下的局部特征匹配 摘要 - 我们介绍 LightGlue,一个深度神经网络,学习匹配图像中的局部特征。我们重新审视 SuperGlue 的多重设计决策,稀疏匹配的最新技术,并得出简单而有效的改进。累积起来,它们使 Lig…...

iOS开发-CAShapeLayer与UIBezierPath实现微信首页的下拉菜单效果
iOS开发-CAShapeLayer与UIBezierPath实现微信首页的下拉菜单效果 之前开发中遇到需要使用实现微信首页的下拉菜单效果。用到了CAShapeLayer与UIBezierPath绘制菜单外框。 一、效果图 二、CAShapeLayer与UIBezierPath 2.1、CAShapeLayer是什么? CAShapeLayer继承自…...

《Elasticsearch 源码解析与优化实战》第5章:选主流程
《Elasticsearch 源码解析与优化实战》第5章:选主流程 - 墨天轮 一、简介 Discovery 模块负责发现集群中的节点,以及选择主节点。ES 支持多种不同 Discovery 类型选择,内置的实现称为Zen Discovery ,其他的包括公有云平台亚马逊的EC2、谷歌…...

Spring Cloud Alibaba - Nacos源码分析(三)
目录 一、Nacos客户端服务订阅的事件机制 1、监听事件的注册 2、ServiceInfo处理 serviceInfoHolder.processServiceInfo 一、Nacos客户端服务订阅的事件机制 Nacos客户端订阅的核心流程:Nacos客户端通过一个定时任务,每6秒从注册中心获取实例列表&…...

DOCKER镜像和容器
1.前言 初见DOCKER,感觉和我们常用的虚拟机(VMware,viurebox)类似,是一个独立于宿主机的模块,可以解决程序在各个系统间的移植,但它真的仅仅是这样嘛? 2.容器的优缺点 1.1.容器…...

探索网页原型设计:构建出色的用户体验
在当今数字化时代,用户对网页体验的要求日益提高。在网页设计过程中,扮演着至关重要的角色。通过网页原型设计,产品经理能够更好地展示和传达网页的整体布局、导航结构、元素位置和交互效果,从而使团队成员更清晰地了解设计意图&a…...

48,排序算法merge
功能描述: 两个容器元素合并,并储存到另一容器中 函数原型: merge(iterator beg1,iterator end1,iterator beg2,iterator end2,iterator dest); //容器元素合并,并存储到另一个容器中 //注意:两个容器必须是有序的…...

【MySQL】复合查询
复合查询目录 一、基本查询二、多表查询三、自连接四、子查询4.1 单行子查询4.2 多行子查询4.3 多列子查询4.4 在from子句中使用子查询4.5 合并查询4.5.1 union4.5.2 union all 五、实战OJ 一、基本查询 --查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的…...

JavaScript中的this指向及绑定规则
在JavaScript中,this是一个特殊的关键字,用于表示函数执行的上下文对象,也就是当前函数被调用时所在的对象。由于JavaScript的函数调用方式多种多样,this的指向也因此而变化。本文将介绍JavaScript中this的指向及绑定规则…...

css中预编译理解,它们之间区别
css预编译? css预编译器用一种专门的编程语言,它可以对web页面样式然后再编译成正常css文件,可以更加方便和高效的编写css代表。主要作用就是为css提供了变量,函数,嵌套,继承,混合等功能&#…...

如何使用Java处理JSON数据?
在Java中,您可以使用许多库来处理JSON数据。以下是使用一种常见的库 Gson 的示例: 首先,确保您已经将 Gson 库添加到您的项目中。您可以在 Maven 中添加以下依赖项: <dependency><groupId>com.google.code.gson<…...

java设计模式-观察者模式
什么是观察者模式 观察者模式(Observer)是软件设计中的一种行为模式。 它定义了对象之间的一对多关系,其中如果一个对象改变了状态,所有依赖它的对象都会自动被通知并更新。 这种模式包含了两种主要的角色,即被观察…...

HiveSQL SparkSQL中常用知识点记录
目录 0. 相关文章链接 1. hive中多表full join主键重复问题 2. Hive中选出最新一个分区中新增和变化的数据 3. Hive中使用sort_array函数解决collet_list列表排序混乱问题 4. SQL中对小数位数很多的数值转换成文本的时候不使用科学计数法 5. HiveSQL & SparkSQL中炸裂…...

mac不识别移动硬盘导致无法拷贝资源
背景 硬盘插入到Mac电脑上之后,mac不识别移动硬盘导致无法拷贝资源。 移动硬盘在Mac上无法被识别的原因可能有很多,多数情况下,是硬盘的格式与Mac电脑不兼容。 文件系统格式不兼容 macOS使用的文件系统是HFS或APFS,如果移动硬盘是…...

Opencv的Mat内容学习
来源:Opencv的Mat内容小记 - 知乎 (zhihu.com) 1.Mat是一种图像容器,是二维向量。 灰度图的Mat一般存放<uchar>类型 RGB彩色图像一般存放<Vec3b>类型。 (1)单通道灰度图数据存放样式: (2)RGB三通道彩色图存放形式不同&#x…...

MySQL~数据库的设计
二、数据库的设计 1、多表之间的关系 1.1 三种分类 一对一: 分析:一个人只有一个身份证,一个身份证只能对应一个人 如:人和身份证 一对多: 如:部门和员工 分析:一个部门有多个员工ÿ…...

开源了!最强原创图解八股文面试网来袭
强烈推荐 Github上业内新晋的一匹黑马—Java图解八股文面试网—Java2Top.cn,图解 Java 大厂面试题,深入全面,真的强烈推荐~ 这是一个二本逆袭阿里的大佬根据自己秋招上岸所看过的相关专栏,面经,课程,结合自…...

微信小程序开发6
一、分包-基础概念 1.1、什么是分包 分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。 1.2、分包的好处 对小程序进行分包的好处主要有以下两点: 可以优化小程序…...

JS 根据身份证号获取年龄、性别、出生日期
先说一代身份证和二代身份证的区别: 1.编号位数不同,第一代身份证为15位号码,第二代证是18位号码 2.编码规则不同,第一代身份证在前6位号码后没有完整出生年份,而二代的有完整的出生年份,一代身份证将年份前二位省略…...

Python+Mongo+LSTM(GTP生成)
下面是一个简单的示例来展示如何使用Python和MongoDB来生成LSTM预测算法。 首先,我们需要安装pymongo和tensorflow库,可以使用以下命令进行安装: pip install pymongo tensorflow接下来,我们连接到MongoDB数据库并获取需要进行预…...

关于idea如何成功运行web项目
导入项目 如图 依次选择 file - new - Project from Existing Sources 选择存放的项目目录地址 如图 导入完成 点击ok 如图 依次选择 Create project from existing sources 点击next如图 ,此处默认即可 点击 next如图 点击next有该提示 是因为之前导入过…...

python读取json文件
import json# 文件路径(同目录文件名即可,不同目录需要绝对路径) path 1.json# 读取JSON文件 with open(path, r, encodingutf-8) as file:data json.load(file)#data为字典 print(data) print(type(data))...

迁移学习、微调、计算机视觉理论(第十一次组会ppt)
@TOC 数据增广 迁移学习 微调 目标检测和边界框 区域卷积神经网络R—CNN...

特殊矩阵的压缩存储
1 数组的存储结构 1.1 一维数组 各数组元素大小相同,且物理上连续存放。第i个元素的地址位置是:a[i] LOC i*sizeof(ElemType) (LOC为起始地址) 1.2 二维数组 对于多维数组有行优先、列优先的存储方法 行优先:先行后列,先存储…...

【网络原理】 (1) (应用层 传输层 UDP协议 TCP协议 TCP协议段格式 TCP内部工作机制 确认应答 超时重传 连接管理)
文章目录 应用层传输层UDP协议TCP协议TCP协议段格式TCP内部工作机制确认应答超时重传 网络原理部分我们主要学习TCP/IP协议栈这里的关键协议(TCP 和 IP),按照四层分别介绍.(物理层,我们不涉及). 应用层 我们需要学会自定义一个应用层协议. 自定义协议的原因? 当前的软件(应用…...

【SQL语句】
目录 一、SQL语句类型 1.DDL 2.DML 3.DLL 4.DQL 二、数据库操作 1.查看 2.创建 2.1 默认字符集 2.2 指定字符集 3.进入 4.删除 5.更改 5.1 库名称 5.2 字符集 三、数据表操作 1.数据类型 1.1 数值类型(常见,下同) 1.1.1 T…...

自动驾驶和机器人学习和总结专栏汇总
汇总如下: 一. 器件选型心得(系统设计)--1_goldqiu的博客-CSDN博客 一. 器件选型心得(系统设计)--2_goldqiu的博客-CSDN博客 二. 多传感器时间同步方案(时序闭环)--1 三. 多传感器标定方案&…...

【C++初阶】C++基础(下)——引用、内联函数、auto关键字、基于范围的for循环、指针空值nullptr
目录 1. 引用 1.1 引用概念 1.2 引用特性 1.3 常引用 1.4 使用场景 1.5 传值、传引用效率比较 1.6 引用和指针的区别 2. 内联函数 2.1 概念 2.2 特性 3.auto关键字(C11) 3.1 类型别名思考 3.2 auto简介 3.3 auto的使用细则 3.4 auto不能推…...

OSI 7层模型 TCPIP四层模型
》Ref: 1. 这个写的嘎嘎好,解释了为啥4层7层5层,还有数据包封装的问题:数据包在网络中的传输过程详解_数据包传输_张孟浩_jay的博客-CSDN博客 2. HTTP协议 与 TCP协议 的区别,作为web程序员必须要懂 - 知乎 (zhihu.com) 3. 数据…...