콘텐츠로 이동

Bruno 명령어

Bruno는 API 테스팅 및 개발을 위해 설계된 경량의 오픈소스 Git 네이티브 API 클라이언트입니다. Postman과는 달리 Bruno는 컬렉션을 평문 파일(.bru 형식)로 저장하므로 Git으로 버전 관리할 수 있어 팀 협업 및 CI/CD 파이프라인에 이상적입니다.

설치

플랫폼명령어
macOS (Homebrew)brew install bruno
Linux (Snap)snap install bruno
Linux (APT)sudo apt-get install bruno
Windows (Chocolatey)choco install bruno
npmnpm install -g @usebruno/cli
다운로드usebruno.com/downloads 방문

시작하기

Bruno GUI 실행

bruno

새 컬렉션 생성

bruno create-collection my-api-collection

기존 컬렉션 열기

bruno /path/to/collection

컬렉션 관리

컬렉션 구조

Bruno는 컬렉션을 .bru 파일이 있는 디렉터리로 저장합니다:

my-api-collection/
├── bruno.json          # Collection metadata
├── environments/
│   ├── Development.json
│   └── Production.json
├── auth/
│   └── auth.bru
└── users/
    ├── get-all-users.bru
    ├── create-user.bru
    └── update-user.bru

Postman에서 가져오기

# Bruno GUI에서: Import → Postman collection JSON 선택
# 또는 CLI 사용 (Bruno 버전에 따라 가능)

폴더에서 요청 정렬하기

컬렉션 내에서 요청을 정렬하려면 폴더를 생성합니다:

  • 컬렉션 트리에서 마우스 우클릭 → New Folder
  • 논리적으로 폴더 이름 지정 (예: users, products, auth)
  • 폴더 간에 요청 드래그

컬렉션 내보내기

# Collections are stored as plain files (.bru format)
# 간단히 Git에 커밋하거나 디렉터리 공유

Bru 언어 (요청 형식)

Bruno는 .bru 파일을 사용합니다—요청을 위한 간단하고 읽기 쉬운 마크업 언어입니다.

기본 요청 파일

meta {
  name: Get All Users
  type: http
  seq: 1
}

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

params:query {
  limit: 10
  offset: 0
}

headers {
  Content-Type: application/json
  User-Agent: Bruno/v1
}

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

본문이 있는 요청

meta {
  name: Create User
  type: http
  seq: 2
}

post {
  url: {{baseUrl}}/api/users
}

headers {
  Content-Type: application/json
}

body:json {
  {
    "name": "John Doe",
    "email": "john@example.com",
    "role": "admin"
  }
}

양식 데이터 요청

meta {
  name: Upload Profile Picture
  type: http
  seq: 3
}

post {
  url: {{baseUrl}}/api/users/{{userId}}/avatar
}

body:form-urlencoded {
  username: johndoe
  email: john@example.com
}

양식 Multipart (파일 업로드)

meta {
  name: Upload File
  type: http
  seq: 4
}

post {
  url: {{baseUrl}}/api/files/upload
}

body:multipartForm {
  file: @/path/to/file.pdf
  description: My document
}

CLI 명령어

컬렉션 또는 요청 실행

# 전체 컬렉션 실행
bru run /path/to/collection

# 특정 요청 실행
bru run /path/to/collection/requests/get-users.bru

# 특정 환경으로 실행
bru run /path/to/collection --env Production

# JSON reporter 형식으로 실행
bru run /path/to/collection --reporter json

# HTML 보고서로 실행
bru run /path/to/collection --reporter html --output report.html

사용 가능한 Reporter

Reporter명령어
CLI (기본값)bru run collection --reporter cli
JSONbru run collection --reporter json
HTMLbru run collection --reporter html --output report.html
JUnitbru run collection --reporter junit

변수를 사용하여 실행

# 환경 변수 전달
bru run /path/to/collection --env Development

# 특정 변수 재정의
bru run /path/to/collection --env Production --variable apiKey=abc123

오류 시 실패

# 테스트가 실패하면 0이 아닌 상태로 종료 (CI/CD에 유용)
bru run /path/to/collection --failOnError

상세 출력

# 상세한 요청/응답 정보 표시
bru run /path/to/collection --verbose

환경 변수

환경 파일 생성

환경 파일은 environments/EnvName.json으로 저장됩니다:

{
  "baseUrl": "https://api.example.com",
  "apiKey": "your-api-key-here",
  "authToken": "bearer-token",
  "userId": "12345",
  "timeout": 5000
}

요청에서 변수 사용

get {
  url: {{baseUrl}}/api/users/{{userId}}
  timeout: {{timeout}}
}

headers {
  Authorization: Bearer {{authToken}}
  X-API-Key: {{apiKey}}
}

환경 전환

# Via CLI
bru run /path/to/collection --env Development

# Via GUI: Bruno 인터페이스에서 환경 드롭다운 선택

환경 변수 유형

유형예제사용법
String"apiKey": "abc123"{{apiKey}}
Number"timeout": 5000{{timeout}}
Boolean"debug": true{{debug}}
Object"config": {...}스크립팅으로 접근

보안 정보 관리

# 민감한 데이터를 위한 .env 파일 생성 (.gitignore에 추가)
echo "PROD_API_KEY=secret123" > .env

# 환경 파일에 참조로 사용
# 또는 Bruno의 GUI를 사용하여 필드를 "Secret"으로 표시

요청 전 스크립트

요청이 전송되기 전에 JavaScript를 추가합니다:

// 동적 값 설정
bru.setEnvVar('timestamp', Date.now());
bru.setEnvVar('nonce', Math.random().toString(36).substring(7));

// 조건부 논리
if (bru.getEnvVar('env') === 'production') {
  bru.setEnvVar('timeout', 10000);
}

// 디버그 정보 로깅
console.log('Sending request to', bru.getEnvVar('baseUrl'));

응답 후 스크립트

응답을 받은 후 JavaScript를 실행합니다:

// 응답 데이터 접근
const responseData = res.getBody();
const statusCode = res.getStatus();
const headers = res.getHeaders();

// 다음 요청을 위한 값 저장
if (statusCode === 200) {
  bru.setEnvVar('authToken', responseData.token);
  bru.setEnvVar('userId', responseData.user.id);
}

// 응답 로깅
console.log('Status:', statusCode);
console.log('Response:', JSON.stringify(responseData, null, 2));

일반적인 응답 작업

// 상태 코드 가져오기
const status = res.getStatus();

// 본문을 문자열로 가져오기
const body = res.getBody();

// JSON 본문 파싱
const data = res.getBody(true); // true = JSON으로 파싱

// 헤더 가져오기
const contentType = res.getHeader('content-type');

// 특정 헤더 가져오기
const authHeader = res.getHeader('authorization');

검증 및 테스트

내장 테스트 검증

// 상태 코드 검증
tests['Status is 200'] = (res.getStatus() === 200);

// 응답 본문 포함 확인
tests['Response contains user'] = res.getBody().includes('john');

// JSON 응답 검증
const data = res.getBody(true);
tests['User ID exists'] = data.user && data.user.id > 0;

// 응답 시간
tests['Response time < 500ms'] = res.getResponseTime() < 500;

// 헤더 검증
tests['Content-Type is JSON'] = res.getHeader('content-type').includes('application/json');

복잡한 테스트 예제

const data = res.getBody(true);

tests['Status is 201'] = res.getStatus() === 201;
tests['ID is a number'] = typeof data.id === 'number';
tests['Email is valid'] = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(data.email);
tests['Created at is ISO date'] = !isNaN(Date.parse(data.createdAt));

// 다음 요청을 위해 저장
if (tests['Status is 201']) {
  bru.setEnvVar('newUserId', data.id);
}

인증

Bearer Token

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

기본 인증

auth:basic {
  username: {{username}}
  password: {{password}}
}

API Key (Header)

headers {
  X-API-Key: {{apiKey}}
  Authorization: ApiKey {{apiKey}}
}

API Key (Query Parameter)

params:query {
  api_key: {{apiKey}}
  apiToken: {{token}}
}

OAuth 2.0

auth:oauth2 {
  grant_type: authorization_code
  authorization_url: https://provider.com/oauth/authorize
  token_url: https://provider.com/oauth/token
  client_id: {{clientId}}
  client_secret: {{clientSecret}}
  scope: read write
}

Digest 인증

auth:digest {
  username: {{username}}
  password: {{password}}
}

요청 유형 및 메서드

GET 요청

meta {
  name: Fetch User
  type: http
  seq: 1
}

get {
  url: {{baseUrl}}/api/users/{{userId}}
}

params:query {
  includeProfile: true
  fields: id,name,email
}

POST 요청

post {
  url: {{baseUrl}}/api/users
}

body:json {
  {
    "name": "Jane Doe",
    "email": "jane@example.com"
  }
}

PUT/PATCH 요청

put {
  url: {{baseUrl}}/api/users/{{userId}}
}

body:json {
  {
    "name": "Jane Smith",
    "status": "active"
  }
}

DELETE 요청

delete {
  url: {{baseUrl}}/api/users/{{userId}}
}

헤더가 있는 요청

headers {
  Content-Type: application/json
  Accept: application/json
  User-Agent: Bruno/v1.0
  X-Request-ID: {{requestId}}
  Authorization: Bearer {{token}}
}

고급 기능

컬렉션 레벨 변수

bruno.json에서 변수 정의:

{
  "name": "My API Collection",
  "version": "1.0",
  "variables": {
    "baseUrl": "https://api.example.com",
    "version": "v1",
    "defaultTimeout": 5000
  }
}

요청 시퀀싱

CLI runner에서 요청 실행 순서 제어:

meta {
  name: Authenticate
  type: http
  seq: 1
}
meta {
  name: Get User Data
  type: http
  seq: 2
}

요청은 seq 순서로 실행됩니다.

GraphQL 쿼리

meta {
  name: GraphQL Query
  type: http
  seq: 1
}

post {
  url: {{baseUrl}}/graphql
}

body:graphql {
  query {
    user(id: "{{userId}}") {
      id
      name
      email
    }
  }
}

쿼리 매개변수

params:query {
  page: 1
  limit: 10
  sort: -createdAt
  filter: status:active
}

Git 워크플로우

Git 네이티브가 중요한 이유

# Collections stored as text files
.bru/
├── users/
   ├── get-user.bru
   └── create-user.bru
└── products/
    └── list-products.bru

# 쉽게 버전 관리
git add .
git commit -m "Update API requests"
git push origin main

# 병합 충돌은 관리 가능
# PR에서 변경 사항 검토
# 팀과 협업

협업 워크플로우

# 팀 멤버가 컬렉션 클론
git clone https://github.com/team/api-collection.git
cd api-collection

# Bruno CLI 설치
npm install -g @usebruno/cli

# 로컬에서 테스트 실행
bru run . --env Development

# 변경 수행
# 새 요청 추가 또는 기존 요청 업데이트

# 커밋 및 푸시
git add .
git commit -m "Add payment API endpoints"
git push origin feature/payments

민감한 파일 무시

# 컬렉션 루트의 .gitignore
.env
.env.local
environments/Production.json
!environments/Production.json.example
secrets/
node_modules/

CI/CD 통합

GitHub Actions

name: API Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install Bruno CLI
        run: npm install -g @usebruno/cli

      - name: Run API tests
        run: bru run . --env CI --reporter json --output test-results.json

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: test-results
          path: test-results.json

GitLab CI

api-tests:
  image: node:18
  script:
    - npm install -g @usebruno/cli
    - bru run . --env CI --reporter json --output test-results.json
  artifacts:
    paths:
      - test-results.json
    reports:
      junit: test-results.json

로컬 Pre-commit Hook

#!/bin/bash
# .git/hooks/pre-commit

echo "Running API tests..."
bru run . --env Development --failOnError

if [ $? -ne 0 ]; then
  echo "API tests failed. Commit aborted."
  exit 1
fi

일반적인 워크플로우

REST API 테스트

# 1. 환경 설정
bru run /path/to/collection --env Development

# 2. 테스트 결과 확인
# 출력에 표시되는 테스트 통과/실패

# 3. 보고서 생성
bru run /path/to/collection --env Development --reporter html --output report.html

병렬 요청으로 부하 테스트

// 요청 전 스크립트에서
for (let i = 0; i < 10; i++) {
  bru.setEnvVar('iteration', i);
}

동적 데이터 생성

// 요청당 고유한 이메일 생성
const timestamp = Date.now();
bru.setEnvVar('dynamicEmail', `user_${timestamp}@example.com`);

// 임의 ID 생성
bru.setEnvVar('randomId', Math.floor(Math.random() * 10000));

요청 연결

// 첫 번째 요청의 응답 후 스크립트
const data = res.getBody(true);
bru.setEnvVar('userId', data.id);
// 다음 요청은 {{userId}} 사용

디버깅

Verbose 모드 활성화

bru run /path/to/collection --verbose

요청/응답 세부 정보 보기

Bruno GUI에서:

  • 요청 클릭
  • “Params” 탭에서 쿼리 매개변수 보기
  • “Body” 탭에서 요청 본문 보기
  • “Response” 탭에서 응답 데이터 보기
  • “Tests” 탭에서 테스트 결과 보기

콘솔 로깅

// 요청 전 또는 응답 후 스크립트
console.log('Variable value:', bru.getEnvVar('baseUrl'));
console.log('Full response:', res.getBody());
console.log('Status code:', res.getStatus());

네트워크 검사

// 응답 헤더 확인
const headers = res.getHeaders();
console.log('All headers:', headers);

// 응답 시간 확인
console.log('Response time:', res.getResponseTime(), 'ms');

파일 구조 모범 사례

api-collection/
├── README.md                 # Documentation
├── .gitignore               # Ignore sensitive files
├── bruno.json               # Collection metadata
├── environments/            # Environment files
│   ├── Development.json
│   ├── Staging.json
│   └── Production.json
├── globals.json             # Global variables
├── auth/
│   ├── login.bru
│   └── refresh-token.bru
├── users/
│   ├── get-all-users.bru
│   ├── get-user-by-id.bru
│   ├── create-user.bru
│   ├── update-user.bru
│   └── delete-user.bru
├── products/
│   ├── list-products.bru
│   └── get-product.bru
└── scripts/
    ├── test-runner.js
    └── helpers.js

리소스

리소스URL
공식 웹사이트usebruno.com
GitHub 저장소github.com/usebruno/bruno
문서docs.usebruno.com
다운로드usebruno.com/downloads
Discord 커뮤니티discord.gg/usebruno
Bru 언어 명세github.com/usebruno/bru
API 테스팅 가이드docs.usebruno.com/api-testing
GitHub 이슈github.com/usebruno/bruno/issues

팁과 트릭

  • 검토를 위한 Git Diff: 컬렉션은 파일이므로 병합 전에 API 변경 사항을 검토하려면 git diff를 사용합니다
  • 환경 템플릿: .example.json 파일을 환경용으로 생성하여 보안 정보 없이 구성 구조를 공유합니다
  • 재사용 가능한 스크립트: 일반적인 테스트 스크립트를 별도 .js 파일에 저장하고 참조합니다
  • 변수 범위: 컬렉션 변수는 전역으로 적용되고 요청 레벨 변수는 이를 재정의합니다
  • 성능: CI/CD에서 --failOnError 플래그를 사용하여 테스트 실패를 조기에 잡습니다
  • 문서화: .bru 파일에 // comment syntax 사용하여 주석을 추가합니다
  • 버전 관리: 버전 간에 쉽게 전환하기 위해 베이스 URL 변수에 API 버전을 포함합니다