微服务契约测试框架-Pact

契约测试

契约测试的思想就是将原本的 Consumer 与 Provider 间同步集成测试,通过契约进行解耦,变成 Consumer 与 Provider 端两个各自独立的、异步单元测试

契约测试的优点:

契约测试与单元测试以及其它测试之间没有重复,它是单纯验证Provider与Consumer之间按预期的方式交互,定位准确;不需要部署真实的系统环境、Mock机制、没有真实API调用,运行非常快、反馈及时、修复周期短、成本低,在这种情况下,自动化测试流水线运行更快了,产品流水线出产品安装包也更快。因此,显然契约测试才是真正对的选择。

契约测试的缺点:

  • 契约测试无法做安全或性能测试等。
  • 契约测试采用Mock机制,所以没有集成测试更接近真实环境,也不能给业务人员做验收,可视性差。
  • 契约测试基于不同的服务使用的协议不同,验证契约的复杂度会不同,复杂度过高时,需要权衡是否有必要加契约测试。

别再加端到端集成测试了,快换契约测试吧 - 简书 (jianshu.com)

基于Consumer驱动的契约测试分两个阶段:

  1. Consumer生成契约,开发者在Consumer端写测试时Mock掉Provider,运行测试生成契约文件;
  2. Provider验证契约,开发者拿契约文件直接在Provider端运行测试进行验证。
  • 契约测试实践篇

PactSpring 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,使单元测试可以通过,并生成契约文件。

(主要是定义契约文件) 

运行单个示例

  1. 切换到所需的示例文件夹cd examples/v3/typescript
  2. 安装所有示例依赖项npm install 
  3. 运行所有示例 -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  TRUEstringprovider的基础url
pactBrokerUrlfalsestringpact broker的base url
providerfalsestringprovider的name
consumerVersionSelectorsfalseConsumerVersionSelector|arraype配置验证的版本
consumerVersionTags falsestring|array使用标签取出最新的契约
providerVersionTagsFALSEstring|array应用到provider的标签
providerVersionBranchFALSEstring分支
includeWipPactsSinceFALSEstring
pactUrlsFALSEarray本地契约文件路径数组或者基于HTTP的url,如果不用Pact Broker则该项必须
providerStatesSetupUrlFALSEstring该参数已废弃
stateHandlersFALSEobject
requestFilterFALSEfunction (Express middleware)改变请求或者输出
beforeEachFALSEfunction在每一个交互验证前执行的函数
afterEachFALSEfunction在每一个交互验证后执行的函数
pactBrokerUsernameFALSEstringPact Broker的验证username
pactBrokerPasswordFALSEstringPact Broker的验证password
pactBrokerTokenFALSEstringPact Broker的验证token
publishVerificationResultFALSEboolean发布验证结果至Broker,只有在持续集成时才设置这个参数
providerVersionFALSEstringprovider的版本
enablePendingFALSEboolean挂起契约
timeoutFALSEnumber超时时间,默认是30秒
logLevelFALSEstring不需要,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!');});});
});

匹配规则

可以使用正则表达式或者基于对象类型匹配或者数组来验证相应的结构
匹配规则取决于契约文件

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Google Earth Engine谷歌地球引擎提取多波段长期反射率数据后绘制折线图并导出为Excel

本文介绍在谷歌地球引擎GEE中&#xff0c;提取多年遥感影像多个不同波段的反射率数据&#xff0c;在GEE内绘制各波段的长时间序列走势曲线图&#xff0c;并将各波段的反射率数据与其对应的成像日期一起导出为.csv文件的方法。 本文是谷歌地球引擎&#xff08;Google Earth Engi…

图为科技T501赋能工业机器人 革新传统工业流程

工业机器人已成为一个国家制造技术与科技水平的重要衡量标准&#xff0c;在2019年&#xff0c;中国工业机器人的组装量与产量均位居了全球首位。 当前&#xff0c;工业机器人被广泛用于电子、物流、化工等多个领域之中&#xff0c;是一种通过电子科技和机械关节制作出来的智能机…

浏览器端代理proxy 解决跨域

一.环境:使用expresshttp-proxy-middleware 直接上代码 // include dependencies const express require( express);//node内置的path模块导入 const path require("path")const { createProxyMiddleware } require( http-proxy-middleware); // 需要代理后端服…

行为型设计模式之策略模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

【万字长文】SpringBoot整合SpringSecurity+JWT+Redis完整教程(提供Gitee源码)

前言&#xff1a;最近在学习SpringSecurity的过程中&#xff0c;参考了很多网上的教程&#xff0c;同时也参考了一些目前主流的开源框架&#xff0c;于是结合自己的思路写了一个SpringBoot整合SpringSecurityJWTRedis完整的项目&#xff0c;从0到1写完感觉还是收获到不少的&…

K8s Service网络详解(二)

K8s Service网络详解&#xff08;二&#xff09; Kube Proxy调度模式Kube-proxy IptablesKube-proxy IPVS Service SelectorPod DNS种常见的 DNS 服务Kube-DNSCoreDNSCorefile 配置 DNS 记录DNS 记录 ServiceDNS 记录 PodDNS 配置策略 Pod 的主机名设置优先级 Ingress Kube Pro…

Appium+python自动化(二十五)-获取控件ID(超详解)

简介 在前边的第二十二篇文章里&#xff0c;已经分享了通过获取控件的坐标点来获取点击事件的所需要的点击位置&#xff0c;那么还有没有其他方法来获取控件点击事件所需要的点击位置呢&#xff1f;答案是&#xff1a;Yes&#xff01;因为在不同的大小屏幕的手机上获取控件的坐…

ModStartCMS v6.9.0 后台多标签改进,主题色自动切换修复

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

Redis学习2--使用java操作Redis

1、java操作Redis库的比较 Redis有各种语言的客户端可以来操作redis数据库&#xff0c;其中java语言主要有Jedis与lettuce &#xff0c;Spring Data Redis封装了上边两个客户端&#xff0c;优缺点如下&#xff1a; 2、使用Jedis操作Redis Jedis使用的基本步骤&#xff1a; 引…

企业服务器数据库被360后缀勒索病毒攻击后采取的措施

近期&#xff0c;360后缀勒索病毒的攻击事件频发&#xff0c;造成很多企业的服务器数据库遭受严重损失。360后缀勒索病毒是Beijingcrypt勒索家族中的一种病毒&#xff0c;该病毒的加密形式较为复杂&#xff0c;目前网络上没有解密工具&#xff0c;只有通过专业的技术人员对其进…

原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)

文章目录 ⭐前言⭐html标签&#x1f496;table表格的属性&#x1f496;实现财务报表 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享原生html——绘制表格报表加水印。 背景&#xff1a;解决没有ps的情况下使用前端html制作表格报表。 html介绍 HTML&#xf…

【Android知识笔记】UI体系(一)

Activity的显示原理 setContentView 首先开发者Activity的onCreate方法中通常调用的setContentView会委托给Window的setContentView方法: 接下来看Window的创建过程: 可见Window的实现类是PhoneWindow,而PhoneWindow是在Activity创建过程中执行attach Context的时候创建的…

Kotlin多平台最佳架构指南

在这篇文章中&#xff0c;我们将对 Kotlin 多平台移动端的最佳架构进行深入探讨。在2023年&#xff0c;作为 Android 开发者&#xff0c;我们会倾向于采用 MVVM 架构&#xff0c;因为它简单、灵活且易于测试。而作为 iOS 开发者&#xff0c;我们可能会选择 MVC、Viper 等架构。…

QT基于TCP协议实现数据传输以及波形绘制

这个玩意我做了两个&#xff0c;一个是安卓app&#xff0c;一个是Windows程序。代码并非全部都是由我从无到有实现&#xff0c;只是实现了我想要的功能。多亏了巨人的肩膀&#xff0c;开源万岁&#xff01;&#xff01;&#xff01; 我把程序放到GitHub上&#xff0c;需要的可…

Vue项目如何生成树形目录结构

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、有兴趣的可以关注一手。 前言 项目的目录结构清晰、可以帮助我们更快理顺项目的整体构成。在写文档之类的时候也比较方便。生成树形目录的方式有多种&#xff0c;我这里简单介绍其中一种较为简单的实现 过…

学习数学助手Schooltech Math Resource Studio 7.0 Crack

数学资源工作室 数学工作表生成器&#xff1a;快速轻松地创建数学工作表 使用易于使用的数学工作表生成器软件创建可打印的数学练习工作表。通过练习、谜题、问题等提高数学技能。 瞄准学习需求并激励学生 Math Resource Studio 是个性化数学教学的理想软件解决方案&#xff0c…

【SDOF振荡器的非线性-非弹性多轴时间响应分析】用于SDOF振荡器非线性非弹性时程分析的鲁棒性分析研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 进行SDOF振荡器的非线性非弹性时程分析的鲁棒性分析研究&#xff0c;旨在探究该方法对不同系统参数和分析条件变化的稳定性和可靠性。以下是一…

几百本常用计算机开发语言电子书链接

GitHub - XiangLinPro/IT_book: 本项目收藏这些年来看过或者听过的一些不错的常用的上千本书籍&#xff0c;没准你想找的书就在这里呢&#xff0c;包含了互联网行业大多数书籍和面试经验题目等等。有人工智能系列&#xff08;常用深度学习框架TensorFlow、pytorch、keras。NLP、…

用HTML写一个简单的静态购物网站

实现代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>购物网站</title> &l…

FRR+VPP

安装 三者的结合&#xff0c;实际上编译安装好就行了&#xff0c;不需要做任何代码上的修改&#xff0c;只需要安装和配置&#xff0c;然后你就有了一台路由器。 FRRouting使用frr-8.5.2版本&#xff0c;VPP使用23.06版本&#xff0c;DPDK和lcpng是VPP的插件&#xff0c;安装…