每次对接一个 API,我都会经历两个阶段。
第一阶段是探索:发请求、看响应、搞清楚接口实际行为。这些年来,这件事发生在 Postman、curl,或者某个不知道藏在哪里的临时文件里。
然后过一段时间,当我需要确保这些接口持续正常工作时,我会打开一个测试文件,从头重写相同的请求。到那时候,探索阶段学到的东西已经忘了一半。
我一直这样做,直到意识到问题不是懒——而是这两个阶段发生在完全不同的工具里,用完全不同的格式。探索阶段的工作永远是一次性的。
这就是我想填补的空白。
"GUI 优先"探索的问题
没错,Postman 已经解决了一部分。如果你只是想发个请求、看看响应,它够用了。
但一旦工作流变得真实起来,我希望探索阶段写的东西本身就是代码:能进 git 的代码、能在 PR 里 review 的代码、能用普通 npm 包的代码、能在 CLI 和 CI 里跑的代码。
现在这一点更重要了,因为代码和 AI 工具配合得更好。AI agent 生成、编辑、重构 TypeScript 比维护一个点击式 UI 工作流容易得多。
核心想法:探索和测试应该是同一个工作流
如果探索和测试是同一件事呢?
不是"把 Postman collection 导出为测试"。不是"录制回放"。而是:在一个真正的代码文件里写一个检查,一键运行,然后保留它。
用 TypeScript 作为探索工具,你能得到:
- 完整的 npm 生态: 需要随机 UUID?
crypto.randomUUID()。需要模拟用户?用faker。 - 类型安全: IDE 会在你点"运行"之前告诉你拼错了 header 或参数。
- 可重构: 想在 50 个测试里改一个 URL?
Cmd+F替换。这就是普通代码——不需要特殊的脚本沙箱。
我做了什么:Glubean
我构建了一个工作流,让"我只是试试"和"我应该保留这个测试"之间的距离尽可能小。
一个测试,一次点击,一条 trace:
import { test } from "@glubean/sdk";
export const getUser = test("get-user", async (ctx) => {
const res = await ctx.http.get("https://dummyjson.com/users/1");
ctx.expect(res).toHaveStatus(200);
const body = await res.json();
ctx.expect(body.id).toBe(1);
});
如果你用过 Jest 或 Vitest 的 VSCode 插件,你知道每个测试旁边会出现一个播放按钮。这里也一样——我点击它,测试运行,一个 Trace Viewer 直接在 VSCode 里打开。
你能看到请求方法、URL、状态码,以及完整的请求/响应体。感觉像 Postman,但驱动它的是你正在编辑的文件。
真实工作流——共享状态、步骤、npm 包:
当我有一个值得保留的检查时,我点击文件级播放按钮,得到一份真正的结果报告。这也是工作流开始和 Postman 不同的地方。
不再是 UI 里的一个保存请求,而是普通的 TypeScript:我可以用 builder API 构建多步骤流程,并在步骤之间传递状态。如果某一步失败,后续步骤自动跳过。
import { test } from "@glubean/sdk";
const API = "https://dummyjson.com";
let userId;
// 简单测试——获取一个已知用户
export const getUser = test("get-user", async (ctx) => {
const res = await ctx.http.get(`${API}/users/1`);
ctx.expect(res).toHaveStatus(200);
const body = await res.json();
userId = body.id;
ctx.log(`Loaded user: ${body.firstName} ${body.lastName}`);
});
// Builder 测试——读取共享的 userId,运行多步骤验证
export const verifyAndUpdate = test("verify-and-update")
.step("检查用户资料", async (ctx) => {
const res = await ctx.http.get(`${API}/users/${userId}`);
ctx.expect(res).toHaveStatus(200);
const body = await res.json();
ctx.expect(body.firstName).toBeDefined();
return { originalName: body.firstName };
})
.step("更新用户名", async (ctx, { originalName }) => {
const res = await ctx.http.put(`${API}/users/${userId}`, {
json: { firstName: "Updated" },
});
ctx.expect(res).toHaveStatus(200);
ctx.log(`Renamed ${originalName} → Updated`);
});
注意: 同一文件中的测试共享模块作用域,按 export 顺序执行——
getUser先跑并设置userId,verifyAndUpdate再使用。共享变量应在模块顶层声明,但只在测试回调内部赋值。详见限制说明。
Builder 的 .step() 链在单个测试内部传递状态。当我运行整个文件时,结果报告会展示每一步的状态和耗时。
这只是 SDK 功能的一小部分——还有 schema 验证、数据驱动测试、自定义指标、重试、浏览器/GraphQL/gRPC 插件支持等等。但这篇文章的重点是工作流,不是功能清单。
我在探索阶段写的代码已经是测试代码了。我不需要重写或者转换格式。这就是我想做对的事情。
为什么这很重要:"摩擦税"
旧工作流的真正成本不只是重写花的时间。而是那些你根本懒得变成测试的东西,因为摩擦太大了。
你在探索时检查了一个边界情况(比如"价格为负数会怎样?"),没问题,你继续往前走了。两个月后它坏了。没人发现,因为那个检查是终端历史里的一条 curl 命令,或者 Postman 里一个忘记保存的临时标签页。
如果写检查和保留检查是同一个动作,更多检查就能存活下来。
从 VSCode 到 CI
因为它就是代码,这些文件在 CLI 和 CI 里不做任何修改就能运行。
glubean run tests/
我不想本地探索一种格式、自动化又是另一种格式。不再需要"导出 JSON"或"同步 collection"。你的测试就在它们该在的地方:在你的仓库里,在你的源代码旁边。
同一个文件,终端和 CI 里同样能跑——不需要转换,不需要导出:
# 同一个文件,同样的断言——现在在终端或 CI 管道里跑
# result.json 包含的 trace 和 VSCode Result Viewer 里看到的完全一样
npx glubean run explore/dummyjson/smoke.test.ts --verbose --emit-full-trace --result-json ./smoke.result.json
在 VSCode 里打开任何 .result.json 文件,Glubean 扩展会自动用 Result Viewer 渲染——即使测试是用 CLI 跑的,你也能用完整的可视化界面查看 trace、assertions 和 events。
现在的状态
我正在以 Glubean 的名字构建这个项目——一个免费开源的 SDK + CLI + VSCode 扩展。核心工作流免费:本地 VSCode 运行、CLI 和 CI。Cloud 上传是可选的,起步有免费额度。它的设计也让平台不需要知道你的密钥,但这是另一个话题,值得单独写一篇。项目还很早期,但它已经彻底改变了我开发 API 的方式。我也在探索一个更激进的 closed-loop 方向:不只是让 AI 帮你写检查,而是把失败后的处理尽量推向自愈工作流。这也是为什么我越来越坚定地认为,Glubean 应该长成 SDK + MCP 这个形状,不过这个话题我留到下一篇单独展开。
如果你想试试:
- 安装 Glubean VSCode 扩展。安装后可能需要重新加载 VSCode 窗口。
- 创建一个
.test.js文件(不需要项目配置——只要一个文件)。 - 写一个
ctx.http.get(),然后点击播放按钮。
关于 scratch mode 的限制(TypeScript 类型错误、无 .env 支持),请参阅限制说明。
就是这样。打开一个测试文件,写一个请求,点击播放。
如果你想要可运行的示例而不是玩具代码片段,我还整理了一个 cookbook 仓库,里面有你 npm install 后就能在 VSCode 里运行的模式。
如果你还在 Postman 和测试套件之间来回切换,我很想听听是什么让你留在那里。