跳转至

Bruno: Git 友好的离线 API 测试工具

为什么要学 Bruno

Postman 曾是 API 测试的标配工具,但它的发展方向越来越让开发者不满: - 需要登录账号才能使用完整功能 - 集合(Collection)存储在云端,有隐私顾虑 - 免费版功能不断缩减 - 协作功能强制收费

Bruno 是一个开源的 API 测试客户端,核心理念:你的 API 集合就是项目中的文件,用 Git 管理

维度PostmanBruno
数据存储云端本地文件系统
版本控制内置(付费)Git 原生
离线使用部分功能完全离线
账号要求必需不需要
文件格式JSON (私有)Bru 文本格式
协作方式Postman CloudGit 仓库
费用免费版受限完全免费开源
隐私请求可能经过 Postman 云100% 本地

核心概念

白话解释

Bruno 的工作方式非常直接: 1. 在你的项目中创建一个文件夹存放 API 定义 2. 每个 API 请求是一个 .bru 文件(纯文本,可读) 3. 环境变量存在 .bru 文件中 4. 整个文件夹用 Git 管理,和代码一起版本控制

这意味着: - API 定义和代码在同一个仓库中 - PR review 时可以同时 review API 变更 - 新成员 clone 仓库就能直接使用所有 API 集合

核心概念表

概念说明Postman 等价物
Collection一组 API 请求的集合(目录)Collection
Request单个 API 请求(.bru 文件)Request
Environment环境变量配置Environment
Script请求前/后执行的脚本Pre-request/Tests Script
Bru LanguageBruno 的请求描述语言无(Postman 用 JSON)
Runner批量运行请求集合Collection Runner
Assert响应断言Tests
Vars请求间变量传递Variables

Bru 文件格式

meta {
  name: 获取用户列表
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/api/users
  body: none
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

headers {
  Content-Type: application/json
  Accept: application/json
}

query {
  page: 1
  limit: 20
}

script:pre-request {
  bru.setVar("timestamp", Date.now());
}

script:post-response {
  const data = res.getBody();
  bru.setVar("firstUserId", data.users[0].id);
}

assert {
  res.status: eq 200
  res.body.users: isArray
}

tests {
  test("should return users", function() {
    expect(res.status).to.equal(200);
    expect(res.body.users).to.be.an('array');
  });
}

安装配置

安装方式

macOS

brew install --cask bruno

Linux

# Snap
sudo snap install bruno

# AppImage
# 从 https://github.com/usebruno/bruno/releases 下载

# Arch Linux
yay -S bruno-bin

Windows

# Chocolatey
choco install bruno

# Scoop
scoop install bruno

# 或从官网下载安装包

CLI 工具(用于 CI/CD)

npm install -g @usebruno/cli

创建第一个集合

  1. 打开 Bruno
  2. 点击 "Create Collection"
  3. 选择项目目录下的文件夹(如 api-tests/
  4. 开始添加请求

或者手动创建目录结构:

your-project/
├── src/
├── api-tests/           # Bruno 集合目录
│   ├── bruno.json       # 集合配置
│   ├── environments/    # 环境配置
│   │   ├── dev.bru
│   │   └── prod.bru
│   ├── auth/           # 按模块组织
│   │   ├── login.bru
│   │   └── register.bru
│   └── users/
│       ├── list.bru
│       ├── get.bru
│       └── create.bru
├── .gitignore
└── package.json

配置 bruno.json

{
  "version": "1",
  "name": "My API Tests",
  "type": "collection",
  "ignore": ["node_modules", ".git"]
}

配置环境

# environments/dev.bru
vars {
  baseUrl: http://localhost:3000
  authToken: dev-token-12345
}

# environments/prod.bru
vars {
  baseUrl: https://api.example.com
  authToken: {{process.env.PROD_TOKEN}}
}

快速上手

发送第一个请求

在 Bruno GUI 中,点击 "New Request" 或手动创建文件:

# api-tests/health.bru
meta {
  name: Health Check
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/health
  body: none
  auth: none
}

assert {
  res.status: eq 200
}

常见请求类型

GET 请求

meta {
  name: 获取用户详情
  type: http
}

get {
  url: {{baseUrl}}/api/users/{{userId}}
  body: none
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

POST 请求(JSON Body)

meta {
  name: 创建用户
  type: http
}

post {
  url: {{baseUrl}}/api/users
  body: json
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

body:json {
  {
    "name": "张三",
    "email": "zhangsan@example.com",
    "role": "admin"
  }
}

PUT 请求(表单数据)

meta {
  name: 更新头像
  type: http
}

put {
  url: {{baseUrl}}/api/users/{{userId}}/avatar
  body: multipartForm
  auth: bearer
}

auth:bearer {
  token: {{authToken}}
}

body:multipart-form {
  avatar: @file(./test-avatar.png)
  description: 新头像
}

使用变量

# 在 URL 中使用变量
get {
  url: {{baseUrl}}/api/users/{{userId}}
}

# 在 Header 中使用变量
headers {
  Authorization: Bearer {{authToken}}
  X-Request-Id: {{$guid}}
}

# 在 Body 中使用变量
body:json {
  {
    "name": "{{userName}}",
    "timestamp": "{{$timestamp}}"
  }
}

内置变量: | 变量 | 说明 | |------|------| | {{$guid}} | 生成 UUID | | {{$timestamp}} | 当前时间戳 | | {{$isoTimestamp}} | ISO 格式时间 | | {{$randomInt}} | 随机整数 |


进阶用法

请求脚本

Pre-request Script(请求前执行)

script:pre-request {
  // 动态生成认证信息
  const crypto = require('crypto');
  const timestamp = Date.now().toString();
  const signature = crypto
    .createHmac('sha256', bru.getEnvVar('apiSecret'))
    .update(timestamp)
    .digest('hex');

  bru.setVar("timestamp", timestamp);
  bru.setVar("signature", signature);
}

Post-response Script(响应后执行)

script:post-response {
  // 提取 token 供后续请求使用
  if (res.status === 200) {
    const body = res.getBody();
    bru.setVar("authToken", body.token);
    bru.setVar("userId", body.user.id);
  }
}

断言(Assert)

assert {
  res.status: eq 200
  res.body.data: isArray
  res.body.data.length: gt 0
  res.body.message: eq "success"
  res.responseTime: lt 2000
  res.headers.content-type: contains application/json
}

断言操作符: | 操作符 | 说明 | |--------|------| | eq | 等于 | | neq | 不等于 | | gt | 大于 | | gte | 大于等于 | | lt | 小于 | | lte | 小于等于 | | contains | 包含 | | isNull | 为 null | | isArray | 是数组 | | isJson | 是 JSON |

测试脚本

tests {
  test("状态码应该是 200", function() {
    expect(res.status).to.equal(200);
  });

  test("返回用户列表", function() {
    const body = res.getBody();
    expect(body.users).to.be.an('array');
    expect(body.users.length).to.be.greaterThan(0);
  });

  test("每个用户都有必要字段", function() {
    const body = res.getBody();
    body.users.forEach(user => {
      expect(user).to.have.property('id');
      expect(user).to.have.property('name');
      expect(user).to.have.property('email');
    });
  });

  test("响应时间在合理范围内", function() {
    expect(res.responseTime).to.be.below(2000);
  });
}

CLI 用于 CI/CD

# 安装 CLI
npm install -g @usebruno/cli

# 运行整个集合
bru run --env dev

# 运行特定目录
bru run api-tests/auth --env dev

# 运行单个请求
bru run api-tests/health.bru --env dev

# 输出 JUnit 格式(CI/CD 集成)
bru run --env dev --format junit --output results.xml

GitHub Actions 集成

# .github/workflows/api-tests.yml
name: API Tests
on: [push, pull_request]

jobs:
  api-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install -g @usebruno/cli
      - run: bru run api-tests --env ci --format junit --output results.xml
      - uses: dorny/test-reporter@v1
        if: always()
        with:
          name: API Test Results
          path: results.xml
          reporter: java-junit

请求链(Chain Requests)

通过脚本和变量,可以串联多个请求:

# 1. login.bru — 先登录获取 token
script:post-response {
  const body = res.getBody();
  bru.setVar("authToken", body.access_token);
}

# 2. get-profile.bru — 用 token 获取用户信息
auth:bearer {
  token: {{authToken}}
}

在 Collection Runner 中按顺序运行这些请求,变量会自动传递。


常见问题

Q1: 从 Postman 迁移到 Bruno 难吗?

Bruno 支持导入 Postman 集合: 1. 在 Postman 中导出集合为 JSON 2. 在 Bruno 中选择 "Import Collection" → "Postman Collection" 3. 选择导出的 JSON 文件

大部分请求、变量、脚本都能自动转换。

Q2: Bruno 支持 WebSocket/gRPC 吗?

Bruno 主要关注 HTTP/REST API。WebSocket 和 gRPC 支持有限或在开发中。如果这些是核心需求,可以考虑搭配其他工具使用。

Q3: 团队如何协作?

通过 Git: 1. Bruno 集合目录放在项目仓库中 2. .bru 文件是纯文本,Git diff 友好 3. PR review 可以同时审查 API 测试变更 4. 不同分支可以有不同的 API 测试版本

Q4: 敏感信息(Token/密码)怎么处理?

# .gitignore 中排除包含敏感信息的环境
api-tests/environments/local.bru

# 使用环境变量引用
# dev.bru
vars {
  apiToken: {{process.env.API_TOKEN}}
}

Q5: Bruno 和 Hurl 有什么区别?

  • Bruno:有 GUI + CLI,适合日常开发和探索性测试
  • Hurl:纯 CLI,纯文本格式,更适合 CI/CD 和自动化测试
  • 两者可以共存:Bruno 用于开发时探索,Hurl 用于 CI/CD 验证

参考资源

资源链接
官方网站https://www.usebruno.com
GitHub 仓库https://github.com/usebruno/bruno
官方文档https://docs.usebruno.com
Bru 语言规范https://docs.usebruno.com/bru-language
CLI 文档https://docs.usebruno.com/cli
Postman 迁移指南https://docs.usebruno.com/importing/postman

总结:Bruno 是 API 测试工具中"数据主权"意识最强的选择。如果你在意 API 定义的版本控制、团队协作、隐私保护,Bruno 的纯文件系统方案显著优于 Postman 的云端方案。它特别适合重视 Git 工作流的开发团队。