基于 Keploy AGENTS.md 的工程实践指南:构建、运行、代码约定与 CI 体系全解
【免费下载链接】keployOpen-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing.项目地址: https://gitcode.com/GitHub_Trending/ke/keploy
本文以仓库根目录的 AGENTS.md 为主体骨架,完整梳理 Keploy 这个"录制-重放"式后端测试工具的工程参考:如何构建与运行keploy单二进制 CLI、各平台的流量拦截差异、用户可见命令速查、磁盘产物格式、仓库特有的 Go 编码约定,以及 CI 矩阵如何保证 mock 格式的前后兼容。读完你不仅能按文档独立搭建、运行 Keploy,还能对照源码定位常见改动的入口目录。
项目定位与入口
AGENTS.md 对项目本身的定义是:Keploy 是一个后端测试工具,它从运行中的应用录制真实 API + 依赖流量,再以 mock 重放为确定性测试。其关键工程特征有三点:
- 流量拦截发生在网络层:Linux 上通过 eBPF 实现(当前树中生成代码位于 pkg/agent/hooks/linux/),macOS/Windows 走用户态代理路径,因此应用无需引入 SDK、无需改代码;
- 产物是一个 Go 编写的单二进制 CLI:
keploy; - Go 模块为
go.keploy.io/server/v3,主入口是 main.go,其中start()最终调用 cli/root.go 中的cli.Root(...)构建 cobra 根命令并执行所有已注册子命令。
AGENTS.md 开篇还确立了一条工作原则:"这里的一切都以仓库当前状态为准——如果下文与磁盘上的内容冲突,相信磁盘,并更新此文件"。后文会看到这条原则在本仓库中有真实体现(例如生成代码路径、lint 排除项的漂移)。
构建:必须带viper_bind_struct标签
文档给出的两条标准构建命令:
# 标准构建(CI 中称为 "build-no-race") go build -tags=viper_bind_struct -o keploy . # 开启竞态检测的构建(CI 中称为 "build",是 CI 矩阵的默认构建) CGO_ENABLED=1 go build -race -tags=viper_bind_struct -o keploy .要点:
viper_bind_struct构建标签是必需的。AGENTS.md 明确警告:漏掉该标签会导致运行时配置字段无法正确绑定(config.Config与 CLI flag 的绑定依赖此 tag 下的 viper 编译变体)。- 版本、Sentry DSN、服务端 URL、GitHub client ID 通过
-ldflags注入,参考 Dockerfile 与 goreleaser.yaml。在 main.go 中可以看到对应的接收端:包级变量version、dsn由setVersion()兜底——本地未注入时version默认为3-dev。
本地运行:平台支持矩阵与 sudo 重执行
平台支持矩阵
"本地跑起来"这件事在各平台并不对称,AGENTS.md 用一张矩阵说明差异(差异的根源是 agent 在每个 OS 上能以何种方式拦截流量):
| 平台 | 原生二进制(应用跑在宿主机) | Keploy 跑在 Docker 内(应用跑在 Docker) |
|---|---|---|
| Linux(x86_64、arm64) | ✅ 支持——eBPF 路径(pkg/agent/hooks/linux/),需要 root | ✅ 支持 |
| Windows(amd64) | ✅ 支持——用户态拦截(文档写作路径为pkg/agent/hooks/winshim/),无需驱动、无需 Administrator;纯 Go + 已提交的 shim DLL,构建无额外拉取步骤 | ✅ 支持 |
| Windows(arm64) | ❌ 落入others兜底桩——Load()/Record()返回 "not supported on non-Linux platforms" | ✅ 支持 |
| macOS(amd64、arm64) | ❌ 同为others兜底桩——macOS 没有原生拦截路径 | ✅ 支持(唯一选择) |
从当前源码结构看,pkg/agent/hooks/下按 build tag 区分hooks_linux.go与hooks_others.go,未支持的架构统一落到 pkg/agent/hooks/others/ 桩实现,与文档中"falls through to theothersstub"的描述一致。
macOS 上的唯一路径是 Docker 模式,AGENTS.md 给出的三步流程:
- 构建镜像:
sudo docker image build -t ghcr.io/keploy/keploy:v3-dev . - 把应用跑进 Docker(通常经
docker compose); - 以该镜像运行 keploy 并挂载到应用容器上——应用若不在 Docker 里,keploy 在 macOS 上无法拦截其流量。这也解释了为什么 CI 里 .github/workflows/prepare_and_run_macos.yml 只调用
golang_docker_macos.yml:不存在 macOS 原生等价工作流。
Linux 两种模式任选(CI 两种都跑:golang_linux.yml+golang_docker.yml);Windows amd64 原生无需 sudo,Docker 模式同样可用。
Linux 原生运行示例:
sudo ./keploy record -c "<your app cmd>" sudo ./keploy test -c "<your app cmd>" --delay 10sudo 自动重执行机制
一个容易踩坑的细节:如果在 Linux 上不带 sudo 传入docker/docker compose作为-c参数,main.go 会在一切初始化之前做"早期检查":utils.ShouldReexecWithSudo()命中后调用utils.ReexecWithSudo(logger),后者用syscall.Exec以sudo -E替换当前进程(实现见 utils/reexec_linux.go)。在 macOS/Windows 上同名 helper 直接短路返回 false(utils/reexec_darwin.go、utils/reexec_windows.go),因为这两个平台依赖当前 Docker 上下文 / Docker Desktop。
Docker 镜像
sudo docker image build -t ghcr.io/keploy/keploy:v3-dev .ghcr.io/keploy/keploy:v3-dev是 CI 为 dev 构建产出的镜像 tag,也是 samples 仓库所期望的 tag。
Lint 与提交规范
golangci-lint(配置 schema v2)
配置文件为 .golangci.yml:
- 启用的 linter:
govet、staticcheck、errcheck、ineffassign、unused; - 格式化工具:
gofmt、goimports; - 从 lint 中排除的路径:生成的 eBPF Go 文件(文档写作时是
pkg/agent/hooks/bpf_arm64_bpfel.go与bpf_x86_bpfel.go)以及pkg/service/utgen。
运行命令:golangci-lint run。
这里恰好是文档"相信磁盘"原则的实例:当前树中这两个生成文件实际位于 pkg/agent/hooks/linux/bpf_arm64_bpfel.go 与 pkg/agent/hooks/linux/bpf_x86_bpfel.go,且.golangci.yml的排除列表仍登记着旧路径。阅读者应以磁盘为准理解"生成代码不可手改"这一规则本身。
Commit hygiene
- .pre-commit-config.yaml 在
commit-msg阶段挂接commitizen(Conventional Commits); - .cz.toml 把约定固定为
cz_conventional_commits,类型使用feat:、fix:、chore:、refactor:、test:、docs:等; - 每个提交必须有正文说明(空一行后写"改了什么、为什么改");
- 每个提交必须
git commit -s签名——它追加Signed-off-by: <user.name> <user.email>trailer,取值来自生效的 git 配置(system →~/.gitconfig→.git/config)。文档明确提示不要手工拼 trailer,让 git 从配置读取身份,保证与 author 一致。
涉及 PR/Issue 时还有一条卫生红线:不要把真实 trace、token、内部主机名或生产日志粘进 PR、Issue、测试与提交的 fixture 中;AGENTS.md 指引读者进一步参考keploy-pr-workflowskill(位于.claude/skills/下)与keploy-docsskill 了解 PR 模板、行为变更时需要同步更新的文档位置。
用户可见命令速查
AGENTS.md 提供了一张"用户视角"命令总表,并规定keploy --help是权威信息源——增删改任何命令时,必须在同一提交里更新此表:
| 命令 | 所在包 | 作用 |
|---|---|---|
keploy record -c "<cmd>" | pkg/service/record | 运行应用,把依赖流量捕获进./keploy/test-set-* |
keploy test -c "<cmd>" | pkg/service/replay | 重放已录制的调用、mock 依赖,写出./keploy/reports/test-run-* |
keploy rerecord -c "<cmd>" | pkg/service/orchestrator | 对新代码重新录制,吸收已接受的变更 |
keploy normalize | pkg/service/tools | 把新观察到的响应接受进 golden 测试用例 |
keploy sanitize | pkg/service/tools | 用custom_gitleaks_rules.toml+ 内置规则清洗机密 |
keploy templatize | pkg/service/tools | 在测试集中把动态值替换为模板 |
keploy config --generate | cli/config.go | 写出默认keploy.yml |
keploy contract ... | pkg/service/contract | OpenAPI 契约生成/测试 |
keploy diff <r1> <r2> | pkg/service/diff | 对两次 test run 做 diff |
keploy report | pkg/service/report | 汇总一次历史 test run |
keploy export/import | cli/export.go、cli/import.go | 在仓库间搬运 test-set |
keploy update | cli/update.go | 自更新二进制 |
keploy agent | cli/agent.go | 内部命令——Docker 镜像 entrypoint 使用 |
文档同时明确标注了一个"看起来像功能但实际不可用"的陷阱:keploy gen(pkg/service/utgen,LLM 单测生成)的 CLI 注册在文档写作时处于被注释状态——实现存在但命令未接线,不要把它当作用户可见功能去宣传。(注:当前树中cli/下已看不到utgen.go,再次印证以磁盘为准。)
磁盘产物格式:脚本解析报告的"地面真值"
keploy record之后:
keploy/ ├── test-set-0/ │ ├── tests/ │ │ ├── test-1.yaml │ │ └── test-2.yaml │ └── mocks.yaml (或 mocks/ 目录,取决于版本) ├── test-set-1/ │ └── ...keploy test之后:
keploy/ └── reports/ └── test-run-0/ (最新 run 编号最大) ├── test-set-0-report.yaml (顶层字段: status: PASSED|FAILED) ├── test-set-1-report.yaml └── coverage.yaml (开启覆盖率时才有)status:行就是脚本 grep 的地面真值。AGENTS.md 指定了标准解析循环的参照实现:.github/workflows/test_workflow_scripts/golang/echo_mysql/golang-linux.sh。
Keploy 仓库特有的编码约定
以下约定全部出自 AGENTS.md 的 "Conventions → Keploy-specific" 一节,每一条都能在源码中找到对应证据:
- 包文档注释——每个
package foo必须以// Package foo ...开头(如 main.go 的// Package main is the entry point...)。 - 根 context——从 utils/ctx.go 的
utils.NewCtx()获取可取消的根 context。它注册了 SIGINT/SIGTERM 信号处理并在收到信号时调用cancel,服务代码中禁止直接使用context.Background()。读源码可以看到它还有两个进阶设计:RegisterPreCancelHook允许子系统在cancel()之前的瞬间同步读取实时状态(例如 agent 在关机瞬间把 syncMock 缓冲状态直写 stderr,因为 zap 的异步日志可能来不及 flush);以及KEPLOY_SIDECAR_DRAIN_SECONDS驱动的 sidecar 优雅排空窗口。 - 协程生命周期——任何 goroutine 都用
errgroup.WithContext(ctx)组织,而不是裸go func()或sync.WaitGroup。工作按"每阶段一个 errgroup"拆分(setup / run-app / req),各阶段拥有独立 cancel,使某阶段可以单独拆除而不拖垮其他阶段。AGENTS.md 指认 pkg/service/record/record.go 为规范布局——当前树中这些 errgroup 分别出现在Start流程的errGrp、runAppErrGrp、setupErrGrp、reqErrGrp(约 L588-L635 一带)。 - 日志——显式传递
*zap.Logger(不用全局变量),经utils/log.New()构建一次;错误上报用utils.LogError(logger, err, "msg", ...fields)替代logger.Error(...)——它会丢弃context.Canceled,避免预期内的关机路径刷爆日志(注意它不设置ErrCode,那是独立机制;实现见 utils/utils.go)。文档还要求:出了问题时,日志应同时告诉用户"下一步该做什么"。 - 退出码——
utils.ErrCode是包级int,main.go 将其传给os.Exit。想让进程非零退出就置 1;文档称目前只有pkg/service/replay/replay.go会在测试失败时翻转它。 - 错误处理——用
fmt.Errorf("...: %w", err)包装;应用生命周期失败用models.AppError+models.AppErrorType分类(字符串枚举位于 pkg/models/errors.go);优先errors.Is/errors.As而非字符串匹配;携带诊断载荷的自定义错误(如mockMismatchError)必须实现Unwrap()。 - 配置访问——服务层从 cli/provider/ 装配好的
*config.Config读取;禁止在pkg/service/或pkg/core/里直接os.Getenv——要新增字段就加进config.Config,在cmdConfigurator/main.go解析并透传。 - 接口住在消费方——每个
pkg/service/<name>/service.go定义本包依赖的小接口(TestDB、MockDB、Telemetry、Instrumentation等),保持 1–10 个方法、按职责塑形;具体实现放在独立包(如 pkg/platform/yaml/testdb/),在 cli/provider/core_service.go 接线。 - Context 感知 I/O——长流程应使用 pkg/platform/yaml/ 中的
ctxReader/ctxWriter,让文件 I/O 也能响应取消。 - 生成代码——eBPF Go 文件
bpf_*_bpfel.go由.c源码生成,永不手改,且在.golangci.yml中被排除;探针行为需要变化时应走 eBPF 工具链重新生成。
通用 Go 卫生规范
AGENTS.md 的 "General Go hygiene" 一节列出了整个代码库遵循的通用规则:
- 接受接口,返回结构体;接口定义在"使用点"而非"实现点"。单一实现且单一消费方时,通常还不需要接口。
context.Context是导出方法的第一个参数(接收者之后、一切之前);context 不得存入 struct。- 不要把状态塞进
context.WithValue——它只承载请求级值(trace ID、认证信息),依赖注入走结构体字段或函数参数。本仓库唯一被容忍的例外是models.ErrGroupKey(携带父 errgroup)。 - 只在边界 panic——库与服务代码返回错误;
recover只出现在顶层 goroutine 入口与main(见utils.Recover,main.go 即以defer utils.Recover(logger)收口)。不要用 panic 表达预期失败。 testify表格驱动测试——fail-fast 用require,需要继续的用assert。文档坦承:本仓库单测覆盖较稀疏,协议级行为主要靠keploy-e2e-testskill 描述的 e2e 验证,因此纯逻辑写表格测试,跨边界行为交给 e2e。- 每个导出符号都要有文档注释,
<Name> does X.句式——godoc 是公共契约。 - 函数保持短小、意图显式——嵌套超过约 3 层或逻辑超过约 50 行就应先拆分;函数命名要值回票价(
parseRecordFrame胜过上方注释块加doStep)。 - 最小化导出面——只有真正需要离开包才大写开头;收缩公共 API 是最便宜的重构。
文档最后留了一句缓冲:"如果你对某条约定没有十足把握,可以忽略它"——约定服务于正确与可维护,而不是教条。
CI 全景:入口、矩阵与兼容性闸门
入口工作流
- .github/workflows/prepare_and_run.yml —— Linux 主 CI,在 PR 到
main与main推送时运行,其余工作流都是它的下游; - .github/workflows/prepare_and_run_macos.yml —— macOS(self-hosted);
- .github/workflows/prepare_and_run_windows.yml —— Windows 等价物;
- .github/workflows/prepare_and_run_integrations.yml —— 私有 parser 专用矩阵子集(面向 integrations 仓库);
- .github/workflows/manual-release.yml —— 仅
workflow_dispatch,触发企业版流水线。
prepare_and_run.yml做什么
- 预先构建三个二进制,供矩阵任务自由组合:
build-no-race:go build -tags=viper_bind_structbuild:go build -race -tags=viper_bind_struct(CGO 开)latest:下载最近的 GitHub release- 三者各自以同名 artifact 上传。
- 构建 Docker 镜像并推送到
ttl.sh,带每次运行唯一的 tag。 - 经
workflow_call扇出到各语言工作流:golang_linux.yml(samples-go,原生 Linux)、golang_docker.yml(Docker 镜像路径)、golang_wsl.yml(WSL)、python_linux.yml/python_docker.yml(samples-python)、node_linux.yml/node_docker.yml(samples-typescript)、java_linux.yml(samples-java)、grpc_linux.yml(gRPC 专用 go 矩阵)、schema_match_linux.yml(schema-match 矩阵,python 侧)、以及专用的fuzzer_linux.yml、node_mapping.yml。 - 收敛到单一
gate任务——它唯一是必需的 status check;单独重跑gate没有意义,它只是复检上游结果。
矩阵结构:保证 mock 格式前后兼容的关键
每个语言工作流都遵循同一矩阵模式:
matrix: app: - name: <display-name> path: <directory in samples-<lang> repo> script_dir: <directory in .github/workflows/test_workflow_scripts/<lang>/> config: - job: record_latest_replay_build # 用发布版录制,用本 PR 构建重放 record_src: latest replay_src: build - job: record_build_replay_latest # 用本 PR 构建录制,用发布版重放 record_src: build replay_src: latest - job: record_build_replay_build # 两端都是本 PR 构建——验证同版本行为 record_src: build replay_src: build这三个config条目是 CI 保证mock 格式向后/向前兼容的机制:任何新变更必须能与上一个发布版二进制双向互操作。如果你的改动会破坏任一方向,就必须加门控(capability 检测、版本检查或 feature flag)——文档给出具体范例risk_profile/golang-linux.sh,其中按case "${REPLAY_BIN:-}" in */build/keploy) ...分支处理。
每个矩阵任务以这样的脚本步骤收尾:
cd samples-<lang>/${{ matrix.app.path }} source $GITHUB_WORKSPACE/.github/workflows/test_workflow_scripts/<lang>/${{ matrix.app.script_dir }}/<lang>-linux.sh脚本通过RECORD_BIN与REPLAY_BIN环境变量接收二进制路径,由./.github/actions/download-binary复合 action 设置。
样例测试脚本的标准结构
所有样例脚本遵循同样的 11 步骨架:
source .../test_workflow_scripts/test-iid.sh——写一个假~/.keploy/installation-id.yaml,避免遥测初始化时交互提示;- 清理上一轮遗留的
keploy/与keploy.yml; - 执行
$RECORD_BIN config --generate,可选地用sed往keploy.yml注入噪声规则(例如global: {"body": {"updated_at":[]}}); - 拉起依赖容器(MySQL、Postgres、Mongo、Redis)并等待就绪;
- 构建样例应用(
go build、mvn package、npm ci、pip install……); - 定义
send_request()——等应用健康检查、灌流量、sleep、按 PID 杀掉 keploy(pgrep keploy); - record在后台跑一次或两次并
tee日志,随后 grep"ERROR"与"WARNING: DATA RACE"(两者都是致命的); - 可选:replay 前停掉 DB 容器(强制 Keploy 走 mock——捕获"mock 漏配"回归);
- 执行replay:
"$REPLAY_BIN" test -c "./app" --delay N --generateGithubActions=false 2>&1 | tee test_logs.txt; - 遍历
./keploy/reports/test-run-*/test-set-*-report.yaml(ls -1dt ... | head -n1取最新),grep 每个报告的status:,出现非PASSED即失败; - 成功退出 0,任一失败退出 1。
CI 拉取的样例仓库
| CI 工作流 | 样例仓库 | 样例位置 |
|---|---|---|
golang_linux.yml、golang_docker.yml、golang_wsl.yml、grpc_linux.yml、schema_match_linux.yml(go 侧) | keploy/samples-go | samples-go/<path> |
python_linux.yml、python_docker.yml、schema_match_linux.yml(python 侧) | keploy/samples-python | samples-python/<path> |
node_linux.yml、node_docker.yml、node_mapping.yml | keploy/samples-typescript | samples-typescript/<path> |
java_linux.yml | keploy/samples-java | samples-java/<path> |
常见改动的入口目录速查
AGENTS.md 的收尾是一张"改 X 从哪看起"的索引表:
| 如果要改…… | 从这里开始 |
|---|---|
| 某协议的录制/重放行为 | pkg/core/proxy/ + pkg/models/ 下对应协议包 |
| mock 匹配逻辑 | pkg/matcher/ + pkg/service/replay/ |
| 磁盘 YAML 格式 | pkg/platform/yaml/ + pkg/models/mock.go、pkg/models/testcase.go |
| CLI flag | cli/ 下对应命令文件 + cli/provider/ |
| 配置默认值 | config/default.go + config/config.go |
| 测试报告 | pkg/service/report/ |
| 覆盖率 | pkg/platform/coverage/ |
| eBPF 探针行为 | pkg/agent/hooks/(C 源码——不要编辑生成的 Go) |
| 给 CI 加样例 | 样例仓库(samples-go等)+.github/workflows/test_workflow_scripts/<lang>/<script_dir>/+ 对应 workflow 里加一条矩阵项 |
文档最后的实践建议:为行为变更补 e2e 覆盖时,优先扩展现有样例及其脚本,而不是新建;完整决策树见keploy-e2e-testskill。
小结
AGENTS.md 的价值在于把"在这个仓库里正确做事"压缩成了一份可执行清单:构建必须带viper_bind_struct、macOS 只能走 Docker、status: PASSED是报告解析的地面真值、errgroup 分阶段管理协程、CI 三向矩阵锁住 mock 格式兼容性。它同时以"相信磁盘"的元规则提醒读者——生成代码路径、lint 排除项这类细节会随仓库演进漂移,动手前以实际文件为准。对贡献者与 AI Agent 而言,这份文档加上文中引用的源码路径,就构成了从构建到提交、从约定到 CI 的完整导航图。
【免费下载链接】keployOpen-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing.项目地址: https://gitcode.com/GitHub_Trending/ke/keploy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考