简介

Keploy 是一款基于 Go 开发的开源 API Testing 工具,它能够在本地开发阶段,录制 API 请求记录,然后生成用于测试的测试用例。生成的测试用例中包含请求和响应参数以及 Mock 等配置,你可以轻松的将这些用例集成到现有的 CI/CD Pipeline 中,从而在 CI/CD 中进一步提升代码的测试覆盖率!

支持的语言:

  • Go
  • C#
  • Rust
  • Java
  • Python
  • JavaScript

Mock 支持的后端数据依赖:

  • HTTP
  • MySQL
  • Redis
  • MongoDB
  • DynamoDB
  • PostgreSQL

架构

Architecture

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…