说实话,我并不是因为 Postman 功能不行才想换它,恰恰相反——是它功能太多、平台味太重了。我换了三台电脑,Postman 一直躺在 dock 栏里,但每次冷启动都要看着它转半天圈,明明只是发一个 GET 请求,却要先登录账号、同步工作区、等插件更新。直到同事丢给我一个 10MB 的替代品,启动不到 1 秒,我才意识到,很多接口调试场景根本不需要背着一个几百 MB 的“全家桶”跑。
这篇博文不劝你彻底卸载 Postman,而是把实际迁移到这款轻量替代品的完整过程写出来:它到底是什么、为什么能这么快、怎么把 Postman 里的存量集合搬过来、日常调试和自动化怎么落地、以及哪些场景下我建议你还是老老实实留在 Postman。
1. 从“全家桶”到“单文件工具”:这个替代品的真实身份
我说的这款替代品,是开源的 Bruno。它主打本地优先、离线优先,和 Postman 最大的区别在于:Postman 是一个云协作平台,Bruno 是一套“接口集合即文件”的轻量工具。
1.1 体积和启动速度的直观对比
先上真实使用中能感知到的对比数据,不同机器、不同版本会有波动,但数量级是明确的:
| 对比维度 | Postman | Bruno |
|---|---|---|
| 安装包大小 | 通常 100MB 以上 | 约 10MB 级别 |
| 冷启动到可用的时间 | 经常要等 3~5 秒,还可能要处理登录态 | 实测接近 1 秒,几乎无感 |
| 空闲内存占用 | 空窗口也能破 300MB | 日常调试几十 MB,大项目也基本不到 100MB |
| 是否强制登录 | 有账号体系,不登录很难受 | 完全不需要账号 |
| 集合存储方式 | 云端 workspace 加本地缓存 | 文件夹加纯文本文件,天然适配 Git |
| 能否纯离线使用 | 受限 | 完整可用 |
我第一次打开 Bruno 时确实愣了一下:窗口弹出来,没有加载条,没有“Checking for updates”,我直接就能在 URL 栏里输入地址回车看响应。这种感觉很像从智能手机回到了一部响应极快的功能机——功能少了很多,但每个操作都干净利落。
1.2 它不是精简版 Postman,而是另一种思路
很多人听到“Postman 替代品”,第一反应是“一个功能被砍了很多的 Postman”。这种理解不对。Bruno 的核心理念和 Postman 完全不同。
在 Postman 里,集合、环境、脚本、Mock、文档、团队权限全都围绕云端账号转;你需要花不少心智去理解 workspace、环境快照、内部 API 同步这些概念。Bruno 的做法很暴力:项目的根目录就是一个集合,每个接口是一个.bru文本文件,环境变量也是一个文本文件。这里没有私有协议、没有云同步、没有账号体系,你甚至可以用任意文本编辑器直接改接口定义。
带来的直接收益是:
- 不联网也能用,公司内网联调没有障碍。
- 接口变更有迹可循,谁改了 URL、谁改了断言,Git 提交记录和 diff 一目了然。
- 换电脑、重装系统,把文件夹复制过去就能继续干活。
- 启动链路里没有后台服务、没有登录态检查、没有云同步任务,自然快。
1.3 先判断你的使用习惯适不适合换
我不建议任何人看完标题就冲动换工具。可以先用下面这个自测清单对号入座:
适合换到 Bruno 的情况:
- 主要工作是 HTTP/REST 接口调试,需要的是“发请求、看响应、存断言”。
- 团队已经有 Git 工作流,想把接口资产纳入版本管理。
- 被 Postman 的登录、同步、更新弹窗烦过。
- 经常在公司内网、离线环境开发。
- 想用命令行跑接口测试,接入 CI/CD。
不适合换的情况:
- 需要把接口文档在线发布成页面,分享给非技术同事。
- 重度依赖 Postman 的 Monaco 编辑体验、可视化脚本文档、在线 Mock Server。
- 团队里已经沉淀了大量
pm.*脚本,重写成本很高。
2. 它凭什么“感觉不到加载”:轻量化背后的设计取舍
启动速度不是靠优化出来的,而是靠“少做事”做出来的。这是我在对比两个工具之后的真实感受。
2.1 启动慢的来源:Postman 花时间初始化了什么
Postman 是 Electron 应用,启动阶段要加载大量 JavaScript 资源、恢复上次工作区、校验登录态、云同步集合、检查插件更新。你看到转圈的那几秒,不是渲染一个窗口那么简单,而是一大堆初始化任务在背后排队。
Bruno 虽然同样基于 Electron 类方案,但它的结构非常克制:启动时只需要读取你指定的本地目录,把.bru文件按文件夹结构列出来,没有账号体系,没有背景同步,没有更新推送。它把需要初始化的东西省到最少,所以能做到接近秒开。
一个很直观的体验差异:我用 Postman 时,如果断网打开,偶尔会看到一个不太自然的“离线模式”提示;用 Bruno 时,断网和不联网根本没有区别,因为它本来就不联网。
2.2 体积到底从哪里省下来的
站在产品角度,Postman 今天已经不是“接口工具”了,而是一个装着协作平台的桌面客户端。为了让不同团队使用 workspace、评论、版本控制、API 网络、第三方集成,它必须塞入大量功能模块和对应的 UI 资源。体积大是必然结果。
Bruno 在这件事上做了非常清醒的取舍。它只保留核心调试链路:方法、URL、Headers、Body、认证、脚本、断言。以“发一个 POST 请求”这个最小场景为例,Bruno 不会在界面上堆几十个按钮,就是几个干净的输入区加一个响应面板。
这里有个容易误会的点:Bruno 不是为了省空间而砍功能,而是先把“接口调试”这个核心场景做到极致,其余能力比如环境变量、断言脚本、CLI 运行,都围绕本地文件格式展开。你可以说它不臃肿,是因为它没有把自己当成一个平台。
2.3 隐性红利:文件格式即文本,版本管理有了实体
真正让我留下的,反而不是启动速度,而是“集合即代码”这件事。
我团队里现在的协作方式是:接口集合放在 Git 仓库里,和前端、后端代码一起做版本管理。后端同事把接口从/v1/user/info改成/v2/user/profile,提交一个 PR,我在 Review 时直接能看到.bru文件的 URL diff。这比在 Postman 里点开“Changelog”再猜谁改了什么透明得多。
用云盘同步或云协作工具时,你永远不知道本地缓存和远端副本哪个是最新的;用 Git 管理文本文件,至少每次冲突都有来源、有记录、可回滚。这是我用了大半年后觉得最值的地方。
提示:正因为 Bruno 把资产都放在本地文件夹里,所以一定记得纳入 Git。如果不用 Git 管理,丢了本地目录就等于丢了全部接口资产。
3. 迁移实操:把 Postman 里的存量资产搬过来
大多数人不会从零开始用接口工具,多少有点 Postman 里的家底。我的迁移路径是:导出集合、导入到 Bruno、重建环境变量、重写脚本断言。
3.1 Postman 侧:导出集合和环境变量
在 Postman 里做几步准备:
- 打开你要迁移的集合,右键集合名,选择 Export。
- 格式选择 Collection v2.1,这是目前兼容性较好的导出格式。
- 如果是环境变量,进入 Environment 页面,点击环境名右侧的下载图标,导出当前环境的 JSON 文件。
- 导出后检查一下文件里是否有大量依赖全局脚本或复杂认证流程的请求,这类请求后面迁移时容易需要人工处理。
导出格式本身不是难点,难点在于你 Postman 集合里的变量引用关系是否干净。如果请求里大量使用{{baseUrl}}、{{token}}这类变量,导入后这些变量不会凭空出现,必须在 Bruno 里重新定义。
3.2 Bruno 侧:导入集合并检查关键字段
Bruno 的使用模型是先建一个本地目录,再把这个目录作为项目打开。你可以新建一个api-tests/目录,然后在 Bruno 里选择打开这个目录,之后用 Import 功能导入 Postman 集合 JSON。
导入完成后,不要急着跑,先抽查最常用的几条请求:
- URL 是否带上了预期变量,比如
{{BASE_URL}}/api/users。 - Method 有没有正确映射,POST、PUT、DELETE 是否都对应。
- Headers 是否完整,尤其是 Content-Type 和认证头。
- Body 格式是否正确,Postman 里的 raw 模式 JSON、form-data 是否被还原。
我踩过的一个坑:导入后部分请求的 Authorization 信息没有完整带过来,尤其是 OAuth 2.0 流程。Bruno 会保留一部分认证配置,但不是所有 Postman 认证方式都能 1:1 转换,这块必须手动确认。
3.3 环境变量重建:不要直接搬,而是按需重建
Postman 导出的环境 JSON 不能直接变成 Bruno 的环境文件。我在迁移时的做法是手动创建一个environment.bru文件,只保留当前还会用到的变量。
比如:
name: dev variables { BASE_URL: https://api.dev.example.com TOKEN: <从 Postman 环境里复制> USER_ID: 123456 }这样做的原因很简单:很多 Postman 环境里存在大量历史遗留变量,比如已经过期的 clientId、临时回调地址、之前联调用的中介变量。全盘搬过去只会让新环境同样臃肿。我按变量在请求里的实际引用情况做了一次清理,项目目录瞬间清爽不少。
如果你不确定哪些变量被引用,建议先全局搜索{{变量名}}字符串,统计出现次数后决定去留。
3.4 脚本和断言要换写法:pm.* 到 assert 的迁移
这是迁移里最容易翻车的地方,也是很多人口中“替代品不顺手”的根源。
Postman 的测试脚本基于 Chai 断言库和pm全局对象,写法长这样:
pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("返回结果包含 data.id", function () { const jsonData = pm.response.json(); pm.expect(jsonData.data.id).to.eql(123); });Bruno 的脚本语法更直接,不依赖那么多链式 API:
assert res.status == 200, 'expected 200'; const jsonData = res.body; assert jsonData.data.id == 123, 'data.id should be 123';第一次迁移时,如果强行把上百条 Postman 脚本逐条翻译,会非常痛苦。我的建议是:先只迁移核心断言,比如状态码、关键字段是否存在、关键字段值是否正确,其余冗余断言全部删掉。等到跑通主链路后再逐步补充。
3.5 cURL 的双向通道
日常工作中,我经常从浏览器开发者工具、Swagger 网页、别人发来的文本里复制 cURL。Bruno 支持直接从 cURL 导入请求,也支持把请求复制成 cURL 再发给别人。这个能力虽然不是吸引我换成它的原因,但确实降低了团队协作里“你发我一段 cURL,我本地调一下”的摩擦。
4. 日常调试的核心路径:变量、断言、环境切换
工具迁移完成后,每天的开发节奏就落在 Bruno 上。这一节把最常用的操作路径完整列出来。
4.1 一个 .bru 文件真的很干净
直接用文本编辑器打开一个 .bru 文件,你会发现它的可读性远超预期:
meta { name: 获取用户详情 type: http seq: 1 } get { url: {{BASE_URL}}/api/users/{{userId}} body: none auth: none } headers { Content-Type: application/json Authorization: Bearer {{TOKEN}} } query { userId: 123 } script { assert res.status == 200, 'status should be 200' assert res.body.data.email != undefined, 'email should exist' }Bruno 的图形界面和这个文本是同步的:你在界面上改 URL、加 Header、写脚本,最终都落到这个文件里。因为结构足够简单,Review 代码时扫一眼就知道这个接口在做什么。
需要提醒的是,不同版本的 .bru 语法可能会有细微差异,比如query块在某些版本里的展示方式不同。我建议以官方文档当前版本为准,这里展示的是稳定核心结构。
4.2 多环境切换:文件化的环境变量更不容易丢
Postman 里切换环境需要打开环境管理界面选择;Bruno 里环境就是项目目录下的一组文件。你可以把 dev、test、prod 三个环境都放在environments/目录里,界面上一键切换。
我比较推荐的目录组织方式:
api-project/ collections/ auth/ login.bru user.bru order/ create.bru list.bru environments/ dev.bru test.bru prod.bru这种结构的好处是,你永远不会出现“我明明改了测试环境的 URL,为什么本地请求还在走生产地址”这种问题。因为每个环境就是一份显式文件,打开看两行就知道选没选对。
4.3 提取返回值:把 token 传给后面的请求
Postman 用户高频搜索“postman 提取返回值”,在 Bruno 里对应的是后置脚本加bru.setVar。
典型场景:先调用登录接口,把返回的 token 保存到变量,后续所有业务请求都用{{TOKEN}}引用。
const jsonData = res.body; if (res.status == 200 && jsonData.token) { bru.setVar('TOKEN', jsonData.token); }实测下来,Bruno 在“登录后拿 token 再请求其他接口”这类请求链上非常顺。要注意的一点是,bru.setVar写出的变量作用域取决于版本和运行方式,如果是在 GUI 中运行,变量会保存在当前环境的运行时里;如果是在 CLI 中跑,可以通过环境文件或命令行参数控制底值。为了保证结果可预期,我通常在脚本里先判断响应状态,再决定是否覆盖变量,避免把异常时的半截 token 存进去。
4.4 常用断言对照表
以下是我迁移时整理的常用断言对照,Postman 和 Bruno 的写法差异一目了然:
| 断言目标 | Postman 写法 | Bruno 写法 |
|---|---|---|
| 状态码为 200 | pm.response.to.have.status(200) | assert res.status == 200 |
| JSON 某个字段存在 | pm.expect(jsonData.data).to.exist | assert res.body.data != undefined |
| JSON 数组长度 | pm.expect(jsonData.items).to.have.lengthOf(3) | assert res.body.items.length == 3 |
| 响应时间低于 500ms | pm.expect(pm.response.responseTime).to.be.below(500) | assert res.duration <= 500 |
| 从响应中设置变量 | pm.environment.set("token", jsonData.token) | bru.setVar("token", jsonData.token) |
不要太迷信某一种写法,脚本引擎也是在快速迭代的,遇到新版本先跑一下简单脚本确认 API 没变,再继续写复杂的。
5. 同样是接口测试,怎么顺手接上 CI
很多人留在一个工具里,是因为担心迁移会影响自动化测试。Bruno 在这方面其实很友好:因为接口集合是纯文本文件,CLI 运行起来完全是标准命令行工具的玩法。
5.1 为什么“集合即代码”对自动化天然友好
Postman 也能用 Newman 跑集合,但集合本身依赖 Postman 账号、环境快照等概念,整个自动化链路还是绕不开 Postman 的云端结构。Bruno 没有这种依赖,你要跑的就是一个目录里的文本文件,输入命令即可执行。
在界面里调试接口和写断言,和最终在 CI 里跑同一套文件,是同一份资产,不存在“界面调试能过、命令行跑不过”的漂移问题。
5.2 在本地用 CLI 跑集合
Bruno 的 CLI 工具是独立的,常用命令大概这样。首先进入项目目录:
cd api-tests运行整个集合:
bru run ./collections/smoke指定环境:
bru run ./collections/smoke --env test输出结果里会逐个列出每个请求的状态码、耗时、断言是否通过。需要供 CI 读取时,也可以输出为 JSON 或 JUnit 格式,实测很方便。
5.3 接入一个 GitHub Actions 示例
下面是一个很基础的接法,核心只有三步:拉代码、装 Bruno CLI、跑集合。
name: api-smoke-test on: push: branches: [ main ] jobs: bru-run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Bruno CLI run: npm install -g @usebruno/cli - name: Run API tests run: bru run "$GITHUB_WORKSPACE/collections/smoke" --env dev装 CLI 的具体方式不局限于 npm,官方还提供了其他安装渠道。核心思路是“在命令行能把集合跑通”,后面接任何 CI 都只是把这条命令放到流水线里。
5.4 参数化运行:用数据文件驱动断言
Bruno 也支持从 CSV 或 JSON 读取测试数据做参数化。我常用的场景是多个用户 ID 跑同一份业务断言,避免把所有用户数据写死在脚本里。
做法是准备好一组数据文件,然后在脚本中获取当前行的字段,例如data.userId。因为格式属于工具特有,细节建议直接参考官方文档,这里只提醒一个点:不要把测试数据写进 .bru 文件本身。否则接口变更和数据变更混在同一个 Git 历史里,review 起来非常痛苦。
5.5 CI 里跑接口测试的几条经验
真正把 Bruno 放进 CI 跑了几个月之后,有几个经验值得分享:
- 给每个请求设置合理的超时时间,CI 机器网络波动比你本地大,超时时间太短会频繁误报。
- 不要在冒烟测试里写会真实写入生产数据的用例,哪怕只是偶然,也应警惕。
- 断言失败信息要写清楚,比如
assert res.body.items.length == 3, 'items length should be 3, but got ' + res.body.items.length。这样才能在 CI 失败时直接从日志判断问题,而不是打开一个庞大的 JSON 响应慢慢翻。
6. 什么情况下不要换?边界、缺陷和我的最终结论
写到这里,该夸的都说完了,但如果你以为我会把这个工具吹成“全面替代 Postman”,那还真不是。它有几条很明确的边界。
6.1 目前还比不了 Postman 的东西
坦白说,Bruno 在以下场景仍然不够看:
- 在线协作与评论:Postman 的账号体系、workspace 成员权限、在线评论是团队管理能力;Bruno 的世界里这些都是靠 Git 的分支、PR、Commit Message 去完成的。
- 文档一键发布:Postman 可以直接把集合发布成在线文档,给不装工具的人点开查看;Bruno 需要你自己用静态站或者接口文档生成工具去实现。
- 可视化测试面板:Postman Runner 的界面和数据图表比命令行输出直观很多,非技术同事看起来也更容易理解。
- 在线 Mock Server:如果你需要部署一个公网可访问的模拟服务,Bruno 做不到开箱即用。
- 存量脚本生态:团队如果有上千条
pm.*脚本,重写成本极高,这种情况下我建议不要贸然切换。
6.2 我实际踩过的几个坑
这些是文档里不会专门写给你看,但真实使用中一定会遇到的:
- 环境文件没选中导致变量变字面量。导入集合后跑请求,结果 URL 里带着
{{BASE_URL}}这样的字面字符串去请求服务端,返回 404。排查了半天才发现是环境选择那里没有选中我建好的 environment。切换环境后立刻正常。 - 依赖 IDE 自动格式化 .bru 文件。有一次代码格式化工具把 .bru 文件的缩进和字段顺序重排了一遍,导致整个文件的 diff 变得巨大,Review 时完全分不清哪些是接口变更,哪些只是格式变化。后来我直接把该文件的格式化排除掉,或者统一固定格式。
- 本地目录同步和 Git 冲突。别把 Bruno 的项目目录丢进 Dropbox、云盘这类自动同步工具里,既然选择了 Git 管理文本文件,就不要再叠加一层同步逻辑,否则会出现大量冲突和重复文件。
- 升级前看 release notes。Bruno 迭代速度不慢,虽然 .bru 格式整体稳定,但字段细节偶尔会有调整。我遇到过新版打开旧文件后 meta 信息多出字段的情况,升级前瞄一眼更新说明能省很多事。
6.3 选型建议
我的最终判断是分人群的:
- 个人项目、中小团队内部联调、需要快速跑接口测试的团队,Bruno 的轻量感和 Git 亲和力会是明显优势。
- 大型团队、需要在线文档和外部分享、已经深度依赖 Postman 平台的,继续用 Postman 更顺畅,不要因为标题里的“10MB”就冲动迁移。
- 两个工具真的可以共存。把 Bruno 作为日常调试的主入口,Postman 仅在你需要在线文档或复杂可视化时要打开一下,完全没问题。
标题说“10MB、启动不到 1 秒”,数字会随版本和平台变化,但背后那种“为单一核心场景设计,不背平台包袱”的思路不会变。我用了大半年,最大的感受其实不是“启动快”,而是工具透明之后,你能把注意力放回接口本身,而不是反反复复和客户端软件的登录、同步、更新较劲。
如果你也被“全家桶式”客户端的重量拖得心烦,我建议不要急着全量迁移,先挑一个小项目,把最常用的一条请求链路用 Bruno 跑通,对比一下日常感受。我自己的体会是,当工具不再刷存在感时,写接口和测接口这件事反而舒服得多。