微服务契约测试框架-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位号码后没有完整出生年份,而二代的有完整的出生年份,一代身份证将年份前二位省略…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...