使用 Keploy 为项目集成 API 测试
简介⌗
Keploy 是一款基于 Go 开发的开源 API Testing 工具,它能够在本地开发阶段,录制 API 请求记录,然后生成用于测试的测试用例。生成的测试用例中包含请求和响应参数以及 Mock 等配置,你可以轻松的将这些用例集成到现有的 CI/CD Pipeline 中,从而在 CI/CD 中进一步提升代码的测试覆盖率!
支持的语言:
- Go
- C#
- Rust
- Java
- Python
- JavaScript
Mock 支持的后端数据依赖:
- HTTP
- MySQL
- Redis
- MongoDB
- DynamoDB
- PostgreSQL
架构⌗
Keploy 的运行模式有两种:
- 记录模式: Keploy 注入 eBPF Hook 来捕获传入的 HTTP 流量,并将传出的 TCP/UDP 流量重定向到其代理服务器。代理服务器异步捕获数据包并将其保存在 YAML 文件中。
- 测试模式: Keploy 读取测试用例和 stubs/mocks 的 YAML 文件。它启动应用程序、发送记录的 HTTP 测试用例并模拟传出调用的响应。这确保不会因非幂等性而产生副作用。
安装⌗
因为 Keploy 底层使用的是 eBPF,所以只能在基于 Linux 的环境上使用,而 Windows 不支持,则只能通过 WSL 来运行 Keploy。macOS 虽然从 Big Sur(11.0)版本开始支持 eBPF,但支持的程度有限。苹果在 macOS 上实现了一种受限的 BPF(称为 Restricted BPF,简称 rbpf),并未完全兼容 Linux 的 eBPF。所以在 macOS 上则是在 Docker 中运行 Keploy。
具体安装发发可以参考官方文档。
实验⌗
克隆官方测示例项目:
docker network create keploy-network
git clone https://github.com/keploy/samples-go.git && cd samples-go/echo-sql
Cloning into 'samples-go'...
remote: Enumerating objects: 2068, done.
remote: Counting objects: 100% (1540/1540), done.
remote: Compressing objects: 100% (769/769), done.
remote: Total 2068 (delta 838), reused 1210 (delta 664), pack-reused 528 (from 1)
Receiving objects: 100% (2068/2068), 120.09 MiB | 15.36 MiB/s, done.
Resolving deltas: 100% (1004/1004), done.
go mod download
docker build -t echo-app:1.0 .
修改 docker-compose.yaml
文件配置,变更后如下:
services:
postgres:
image: postgres:10.5
container_name: postgresDb
restart: always
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
ports:
- '5432:5432'
volumes:
# - ./postgres-data:/var/lib/postgresql/data
# copy the sql script to create tables
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- keploy-network
networks:
keploy-network:
name: keploy-network
external: true
启动服务:
docker compose up
运行 Keploy:
keploy record -c "docker run -p 8082:8082 --name echoSqlApp --network keploy-network echo-app:1.0"
▓██▓▄
▓▓▓▓██▓█▓▄
████████▓▒
▀▓▓███▄ ▄▄ ▄ ▌
▄▌▌▓▓████▄ ██ ▓█▀ ▄▌▀▄ ▓▓▌▄ ▓█ ▄▌▓▓▌▄ ▌▌ ▓
▓█████████▌▓▓ ██▓█▄ ▓█▄▓▓ ▐█▌ ██ ▓█ █▌ ██ █▌ █▓
▓▓▓▓▀▀▀▀▓▓▓▓▓▓▌ ██ █▓ ▓▌▄▄ ▐█▓▄▓█▀ █▓█ ▀█▄▄█▀ █▓█
▓▌ ▐█▌ █▌
▓
version: 2.3.0-beta31
🐰 Keploy: 2024-10-26T08:08:06Z INFO detected that Keploy is running in a docker container
🐰 Keploy: 2024-10-26T08:08:06Z WARN buildDelay is set to 30, incase your docker container takes more time to build use --buildDelay to set custom delay
🐰 Keploy: 2024-10-26T08:08:06Z INFO Example usage: keploy record -c "docker-compose up --build" --buildDelay 35
🐰 Keploy: 2024-10-26T08:08:06Z INFO trying to inject network:keploy-network to the keploy container
🐰 Keploy: 2024-10-26T08:08:06Z INFO Successfully injected network to the keploy container {"Keploy container": "keploy-v2", "appNetwork": "keploy-network", "keploy container ip": "192.168.107.3"}
🐰 Keploy: 2024-10-26T08:08:07Z INFO keploy initialized and probes added to the kernel.
🐰 Keploy: 2024-10-26T08:08:07Z INFO starting UDP DNS server at addr :26789
🐰 Keploy: 2024-10-26T08:08:07Z INFO starting TCP DNS server at addr :26789
🐰 Keploy: 2024-10-26T08:08:07Z INFO Keploy has taken control of the DNS resolution mechanism, your application may misbehave if you have provided wrong domain name in your application code.
🐰 Keploy: 2024-10-26T08:08:07Z INFO Proxy started at port:16789
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:8082
运行后 Keploy 和服务都启动了,接下来就可以向服务发送请求了:
curl --request POST \
--url http://localhost:8082/url \
--header 'content-type: application/json' \
--data '{"url": "https://github.com"}'
{"ts":1729930100440833849,"url":"http://localhost:8082/4KepjkTT"}
curl --request GET \
--url http://localhost:8082/4KepjkTT
当请求发送后,在 Keploy 的控制台中会输出如下日志:
🐰 Keploy: 2024-10-26T08:08:22Z INFO 🟠 Keploy has captured test cases for the user's application. {"path": "/Users/George/Develop/samples-go/echo-sql/keploy/test-set-1/tests", "testcase name": "test-1"}
🐰 Keploy: 2024-10-26T08:09:03Z INFO 🟠 Keploy has captured test cases for the user's application. {"path": "/Users/George/Develop/samples-go/echo-sql/keploy/test-set-1/tests", "testcase name": "test-3"}
到这一步,就成功的拦截了请求,并生成了对应的请求记录 YAML 文件,接下来可以到项目更目录查看生成的文件内容。
第一个 POST 请求:
version: api.keploy.io/v1beta1
kind: Http
name: test-1
spec:
metadata: {}
req:
method: POST
proto_major: 1
proto_minor: 1
url: http://localhost:8082/url
header:
Accept: '*/*'
Content-Length: "33"
Content-Type: application/json
Host: localhost:8082
User-Agent: curl/8.7.1
body: |-
{
"url": "https://github.com"
}
timestamp: 2024-10-26T08:08:20.436143805Z
resp:
status_code: 200
header:
Content-Length: "66"
Content-Type: application/json; charset=UTF-8
Date: Sat, 26 Oct 2024 08:08:20 GMT
body: |
{"ts":1729930100440833849,"url":"http://localhost:8082/4KepjkTT"}
status_message: OK
proto_major: 0
proto_minor: 0
timestamp: 2024-10-26T08:08:22.510298785Z
objects: []
assertions:
noise:
body.ts: []
header.Date: []
created: 1729930102
curl: |-
curl --request POST \
--url http://localhost:8082/url \
--header 'Host: localhost:8082' \
--header 'User-Agent: curl/8.7.1' \
--header 'Accept: */*' \
--header 'Content-Type: application/json' \
--data "{\n \"url\": \"https://github.com\"\n}"
第二个 GET 请求:
version: api.keploy.io/v1beta1
kind: Http
name: test-3
spec:
metadata: {}
req:
method: GET
proto_major: 1
proto_minor: 1
url: http://localhost:8082/4KepjkTT
header:
Accept: '*/*'
Host: localhost:8082
User-Agent: curl/8.7.1
body: ""
timestamp: 2024-10-26T08:09:01.286595576Z
resp:
status_code: 308
header:
Content-Length: "0"
Date: Sat, 26 Oct 2024 08:09:01 GMT
Location: https://github.com
body: ""
status_message: Permanent Redirect
proto_major: 0
proto_minor: 0
timestamp: 2024-10-26T08:09:03.368781888Z
objects: []
assertions:
noise:
header.Date: []
created: 1729930143
curl: |
curl --request GET \
--url http://localhost:8082/4KepjkTT \
--header 'Accept: */*' \
--header 'Host: localhost:8082' \
--header 'User-Agent: curl/8.7.1' \
mocks.yaml 文件:
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-0
spec:
metadata:
type: config
postgresrequests:
- identifier: StartupRequest
length: 102
payload: AAAAZgADAAB1c2VyAHBvc3RncmVzAGRhdGFiYXNlAHBvc3RncmVzAGRhdGVzdHlsZQBJU08sIE1EWQBjbGllbnRfZW5jb2RpbmcAVVRGOABleHRyYV9mbG9hdF9kaWdpdHMAMgAA
startup_message:
protocolversion: 196608
parameters:
client_encoding: UTF8
database: postgres
datestyle: ISO, MDY
extra_float_digits: "2"
user: postgres
auth_type: 0
postgresresponses:
- header: [R]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [145, 212, 36, 22]
msg_type: 82
auth_type: 5
reqtimestampmock: 2024-10-26T08:08:09.723623968Z
restimestampmock: 2024-10-26T08:08:09.725945385Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-1
spec:
metadata:
type: config
postgresrequests:
- header: [p]
identifier: ClientRequest
length: 102
password_message:
password: md56527563748c9648a3030941e6094c992
msg_type: 112
auth_type: 0
postgresresponses:
- header: [R, S, S, S, S, S, S, S, S, S, S, S, K, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
backend_key_data:
process_id: 68
secret_key: 3063486908
parameter_status:
- name: application_name
value: ""
- name: client_encoding
value: UTF8
- name: DateStyle
value: ISO, MDY
- name: integer_datetimes
value: "on"
- name: IntervalStyle
value: postgres
- name: is_superuser
value: "on"
- name: server_encoding
value: UTF8
- name: server_version
value: 10.5 (Debian 10.5-2.pgdg90+1)
- name: session_authorization
value: postgres
- name: standard_conforming_strings
value: "on"
- name: TimeZone
value: UTC
- name: TimeZone
value: UTC
- name: TimeZone
value: UTC
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:09.734120389Z
restimestampmock: 2024-10-26T08:08:09.734230184Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-2
spec:
metadata:
type: config
postgresrequests:
- header: [Q]
identifier: ClientRequest
length: 102
query:
string: ;
msg_type: 81
auth_type: 0
postgresresponses:
- header: [I, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:09.734854874Z
restimestampmock: 2024-10-26T08:08:09.734906876Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-3
spec:
metadata:
type: config
postgresrequests:
- header: [Q]
identifier: ClientRequest
length: 102
payload: UQAAAMsKCQlDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyB1cmxfbWFwICgKCQkJaWQgICAgICAgICAgIFZBUkNIQVIoMjU1KSBQUklNQVJZIEtFWSwKCQkJcmVkaXJlY3RfdXJsIFZBUkNIQVIoMjU1KSBOT1QgTlVMTCwKCQkJY3JlYXRlZF9hdCAgIFRJTUVTVEFNUCBOT1QgTlVMTCwKCQkJdXBkYXRlZF9hdCAgIFRJTUVTVEFNUCBOT1QgTlVMTAoJCSk7CgkA
query:
string: ' CREATE TABLE IF NOT EXISTS url_map ( id VARCHAR(255) PRIMARY KEY, redirect_url VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); '
msg_type: 81
auth_type: 0
postgresresponses:
- header: ["N", C, Z]
identifier: ServerResponse
length: 102
payload: TgAAAHRTTk9USUNFAFZOT1RJQ0UAQzQyUDA3AE1yZWxhdGlvbiAidXJsX21hcCIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nAEZwYXJzZV91dGlsY21kLmMATDIwOQBSdHJhbnNmb3JtQ3JlYXRlU3RtdAAAQwAAABFDUkVBVEUgVEFCTEUAWgAAAAVJ
authentication_md5_password:
salt: [0, 0, 0, 0]
command_complete:
- command_tag_type: CREATE TABLE
notice_response:
severity: NOTICE
severity_unlocalized: NOTICE
code: 42P07
message: relation "url_map" already exists, skipping
detail: ""
hint: ""
position: 0
internal_position: 0
internal_query: ""
where: ""
schema_name: ""
table_name: ""
column_name: ""
data_type_name: ""
constraint_name: ""
file: parse_utilcmd.c
line: 209
routine: transformCreateStmt
unknown_fields: {}
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:09.737489386Z
restimestampmock: 2024-10-26T08:08:09.737585347Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-4
spec:
metadata:
type: config
postgresrequests:
- header: [Q]
identifier: ClientRequest
length: 102
query:
string: ;
msg_type: 81
auth_type: 0
postgresresponses:
- header: [I, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:20.440583674Z
restimestampmock: 2024-10-26T08:08:20.44066376Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-5
spec:
metadata:
type: config
postgresrequests:
- header: [P, D]
identifier: ClientRequest
length: 102
payload: UAAAADcACgkJCVNFTEVDVCAqCgkJCUZST00gdXJsX21hcAoJCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
describe:
object_type: 83
name: ""
parse:
- name: ""
query: ' SELECT * FROM url_map WHERE id = $1 '
parameter_oids: []
msg_type: 68
auth_type: 0
postgresresponses:
- header: ["1", t, T, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
parameter_description:
parameteroids:
- 1042
ready_for_query:
txstatus: 73
row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:20.441889138Z
restimestampmock: 2024-10-26T08:08:20.442930967Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-6
spec:
metadata:
type: config
postgresrequests:
- header: [B, E]
identifier: ClientRequest
length: 102
payload: QgAAABgAAAAAAAEAAAAINEtlcGprVFQAAEUAAAAJAAAAAABTAAAABA==
bind:
- parameters: [[52, 75, 101, 112, 106, 107, 84, 84]]
execute:
- {}
msg_type: 69
auth_type: 0
postgresresponses:
- header: ["2", C, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
command_complete:
- command_tag_type: SELECT 0
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:20.444335976Z
restimestampmock: 2024-10-26T08:08:20.444560068Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-7
spec:
metadata:
type: config
postgresrequests:
- header: [P, D]
identifier: ClientRequest
length: 102
payload: UAAAAGUACgkJSU5TRVJUIElOVE8gdXJsX21hcCAoaWQsIHJlZGlyZWN0X3VybCwgY3JlYXRlZF9hdCwgdXBkYXRlZF9hdCkKCQlWQUxVRVMgKCQxLCAkMiwgJDMsICQ0KQoJAAAARAAAAAZTAFMAAAAE
describe:
object_type: 83
name: ""
parse:
- name: ""
query: ' INSERT INTO url_map (id, redirect_url, created_at, updated_at) VALUES ($1, $2, $3, $4) '
parameter_oids: []
msg_type: 68
auth_type: 0
postgresresponses:
- header: ["1", t, "n", Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
parameter_description:
parameteroids:
- 1042
- 1043
- 1114
- 1114
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:20.445084295Z
restimestampmock: 2024-10-26T08:08:20.445454558Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-8
spec:
metadata:
type: config
postgresrequests:
- header: [B, E]
identifier: ClientRequest
length: 102
payload: QgAAAHIAAAAAAAQAAAAINEtlcGprVFQAAAASaHR0cHM6Ly9naXRodWIuY29tAAAAHjIwMjQtMTAtMjYgMDg6MDg6MjAuNDQwODMzODQ5WgAAAB4yMDI0LTEwLTI2IDA4OjA4OjIwLjQ0MDgzMzg0OVoAAEUAAAAJAAAAAABTAAAABA==
bind:
- parameters: [[52, 75, 101, 112, 106, 107, 84, 84], [104, 116, 116, 112, 115, 58, 47, 47, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109], [50, 48, 50, 52, 45, 49, 48, 45, 50, 54, 32, 48, 56, 58, 48, 56, 58, 50, 48, 46, 52, 52, 48, 56, 51, 51, 56, 52, 57, 90], [50, 48, 50, 52, 45, 49, 48, 45, 50, 54, 32, 48, 56, 58, 48, 56, 58, 50, 48, 46, 52, 52, 48, 56, 51, 51, 56, 52, 57, 90]]
execute:
- {}
msg_type: 69
auth_type: 0
postgresresponses:
- header: ["2", C, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
command_complete:
- command_tag_type: INSERT 0 1
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:20.451974127Z
restimestampmock: 2024-10-26T08:08:20.451997753Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-9
spec:
metadata:
type: config
postgresrequests:
- header: [Q]
identifier: ClientRequest
length: 102
query:
string: ;
msg_type: 81
auth_type: 0
postgresresponses:
- header: [I, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:45.584922216Z
restimestampmock: 2024-10-26T08:08:45.584979593Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-10
spec:
metadata:
type: config
postgresrequests:
- header: [P, D]
identifier: ClientRequest
length: 102
payload: UAAAADQACgkJU0VMRUNUICoKCQlGUk9NIHVybF9tYXAKCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
describe:
object_type: 83
name: ""
parse:
- name: ""
query: ' SELECT * FROM url_map WHERE id = $1 '
parameter_oids: []
msg_type: 68
auth_type: 0
postgresresponses:
- header: ["1", t, T, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
parameter_description:
parameteroids:
- 1042
ready_for_query:
txstatus: 73
row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:45.585466111Z
restimestampmock: 2024-10-26T08:08:45.585555489Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-11
spec:
metadata:
type: config
postgresrequests:
- header: [B, E]
identifier: ClientRequest
length: 102
payload: QgAAABgAAAAAAAEAAAAIR3V3SENnb1EAAEUAAAAJAAAAAABTAAAABA==
bind:
- parameters: [[71, 117, 119, 72, 67, 103, 111, 81]]
execute:
- {}
msg_type: 69
auth_type: 0
postgresresponses:
- header: ["2", C, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
command_complete:
- command_tag_type: SELECT 0
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:08:45.588724562Z
restimestampmock: 2024-10-26T08:08:45.588848191Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-12
spec:
metadata:
type: config
postgresrequests:
- header: [Q]
identifier: ClientRequest
length: 102
query:
string: ;
msg_type: 81
auth_type: 0
postgresresponses:
- header: [I, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
ready_for_query:
txstatus: 73
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:09:01.287327019Z
restimestampmock: 2024-10-26T08:09:01.287367771Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-13
spec:
metadata:
type: config
postgresrequests:
- header: [P, D]
identifier: ClientRequest
length: 102
payload: UAAAADQACgkJU0VMRUNUICoKCQlGUk9NIHVybF9tYXAKCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
describe:
object_type: 83
name: ""
parse:
- name: ""
query: ' SELECT * FROM url_map WHERE id = $1 '
parameter_oids: []
msg_type: 68
auth_type: 0
postgresresponses:
- header: ["1", t, T, Z]
identifier: ServerResponse
length: 102
authentication_md5_password:
salt: [0, 0, 0, 0]
parameter_description:
parameteroids:
- 1042
ready_for_query:
txstatus: 73
row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
msg_type: 90
auth_type: 0
reqtimestampmock: 2024-10-26T08:09:01.287893998Z
restimestampmock: 2024-10-26T08:09:01.287930416Z
connectionId: "0"
可以看到在 mocks.yaml
文件中还记录了 PostgreSQL 中执行的 SQL,这也就是上面说到的在测试模式中确保不会因非幂等性而产生副作用。
CI/CD 集成⌗
.gitlab-ci.yml
:
---
stages:
- test
keploy-test-job: # This job runs in the test stage.
image: ubuntu:latest
stage: test
before_script:
## Add the dependencies && Install Keploy Binary
- apt update && apt install -y sudo curl
- curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz --overwrite -C /tmp
- sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keploy
- sudo mount -t debugfs debugfs /sys/kernel/debug
script:
## Steps to run application
...
在 CI/CD 的测试环节,我们可以将前面录制好的请求记录和 mocks 配置,用于重放请求!
总结⌗
目前我所了解的一些 API Docs 管理工具例如 Postman、Apifix 这类工具,要想集成到现有的 CI/CD Pipeline 中,是有一定成本的。无法做到真正的自托管,而 Kepoly 则可以做到真正意义上的自托管,并且最大程度降低测试的副作用!
这个东西的强大之处就在于,无需代码,和过多的复杂配置,它就能够分析出后端服务所以来的其他服务,例如 API 对于上游服务的请求。这在传统的开发体验中是无法做到的,只能由开发人员手动完成!
I hope this is helpful, Happy hacking…