第2章:シナリオ記述編¶
runnでは、YAMLを使ってテストシナリオを記述します。プログラミング知識がなくても、チーム全員が読み書きできるテストが作成できます。
Runbook - あなたのテストの台本¶
runnでは、テストシナリオをRunbookと呼びます。何をどの順番で実行するかを記述したものです。
Runbookの利点¶
従来のテストコード:
const response = await fetch('/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice' })
});
assert(response.status === 201);
runnのRunbook:
desc: シナリオの説明 # このシナリオが何をテストするか
labels: # シナリオの分類ラベル(フィルタリングに利用)
- api
- user
runners: # 使用するランナーの定義
blog: http://localhost:8080
vars: # シナリオで使用する変数
baseURL: http://localhost:8080
timeout: 30
steps: # 実行するステップの定義
- blog:
/users:
get: {}
test: current.res.status == 200
YAMLで記述することで、より直感的にテストを表現できます。
Runbookの5つの要素¶
Runbookは5つの主要セクションで構成されています。
1. desc - シナリオの目的¶
テストの目的を明確に記述します。
2. labels - テストの分類¶
--label
オプションで特定のテストのみを実行できます。
3. runners - 接続先の定義¶
HTTP、gRPC、データベースなどの接続先を定義します。
4. vars - 変数定義¶
繰り返し使う値を変数として定義できます。
5. steps - 実行ステップ¶
テストの本体となる、順番に実行されるアクションのリストです。
2つの記述スタイル¶
runnは2つの記述スタイルを提供しています。
スタイル1: リスト形式¶
インデックスでステップを参照する、シンプルな方法です。
desc: ユーザー作成とログインのテスト(リスト形式)
runners:
blog: http://localhost:8080
steps:
- blog: # steps[0]
/users:
post:
body:
application/json:
name: "Alice"
email: "alice@example.com"
test: steps[0].res.status == 201
- blog: # steps[1]
/login:
post:
body:
application/json:
email: "{{ steps[0].res.body.email }}"
password: "password123"
test: |
steps[1].res.status == 200 &&
steps[1].res.body.token != null
メリット:
- 学習が簡単
- 短いシナリオに適している
steps[0]
、steps[1]
と直感的に参照できる
スタイル2: マップ形式¶
名前付きステップで、より読みやすくなります。
desc: ユーザー作成とログインのテスト(マップ形式)
runners:
blog: http://localhost:8080
steps:
create_user: # 名前付きステップ
blog:
/users:
post:
body:
application/json:
name: "Alice"
email: "alice@example.com"
test: steps.create_user.res.status == 201
login_user: # 名前付きステップ
blog:
/login:
post:
body:
application/json:
email: "{{ steps.create_user.res.body.email }}"
password: "password123"
test: |
steps.login_user.res.status == 200 &&
steps.login_user.res.body.token != null
メリット:
- ステップの意図が明確
- 長いシナリオでも管理しやすい
steps.create_user
のように意味のある名前で参照できる
10ステップ以上のシナリオでは、マップ形式の使用をお勧めします。
変数の活用¶
変数定義¶
runnの変数システムを使用すると、柔軟な設定が可能です。
vars:
# 静的な値
apiVersion: v1
timeout: 30
# 環境変数から取得
apiKey: ${API_KEY:-test-api-key}
environment: ${ENV:-development} # デフォルト値付き
# 複雑なデータ構造
testUser:
name: "Test User"
email: "test@localhost"
roles:
- admin
- user
runners:
httpbin: http://localhost:8080
steps:
test_vars:
httpbin:
/anything:
get:
headers:
X-API-Key: "{{ vars.apiKey }}"
test: current.res.status == 200
show_vars:
desc: "変数の値を表示"
# 変数の値をデバッグ出力
dump: vars.apiKey
ポイント:
- 環境変数から自動取得(
${API_KEY}
) - デフォルト値の設定(
${ENV:-development}
) - 複雑なデータ構造も定義可能
変数参照¶
定義した変数は以下のように参照できます。
runners:
httpbin: http://localhost:8080
vars:
apiVersion: "v1"
apiKey: "test-api-key"
timeout: "30"
steps:
- httpbin:
/get?v={{ vars.apiVersion }}: # パス内での変数展開
get:
headers:
X-API-Key: "{{ vars.apiKey }}"
X-Timeout: "{{ vars.timeout }}"
test: |
current.res.status == 200
{{ vars.変数名 }}
の記法で変数を参照できます。
ステップ記述の詳細¶
HTTPリクエストの例¶
HTTPリクエストの各種機能を使用した例です。
runners:
blog: http://localhost:8080
vars:
environment: "test"
userId: "123"
token: "test-token"
steps:
# まずユーザーを作成
create_user:
blog:
/users:
post:
body:
application/json:
name: "Test User"
email: "test@localhost"
test: current.res.status == 201
api_call:
desc: ユーザー情報を更新する # ステップの説明
if: vars.environment == "test" # 条件付き実行
blog:
/users/{{ steps.create_user.res.body.id }}:
put:
headers:
Content-Type: application/json
Authorization: "Bearer {{ vars.token }}"
body:
application/json:
name: "Updated Name"
email: "updated@localhost"
timeout: 10s # タイムアウト設定
test: | # テストアサーション
current.res.status == 200 &&
current.res.body.name == "Updated Name"
dump: current.res # デバッグ用の値出力
標準出力:
{
"body": {
"email": "updated@localhost",
"id": 1,
"name": "Updated Name"
},
"cookies": {},
"headers": {
"Content-Length": [
"59"
],
"Content-Type": [
"application/json"
],
"Date": [
"Sun, 27 Jul 2025 23:30:09 GMT"
]
},
"rawBody": "{\"id\":1,\"name\":\"Updated Name\",\"email\":\"updated@localhost\"}\n",
"status": 200
}
利用可能な機能:
- 条件付き実行(
if
) - タイムアウト設定
- テストアサーション
- デバッグ用のダンプ機能
データベースクエリ¶
SQLクエリもYAMLで記述できます。
# TODO: Run this example in a test database
runners:
testdb: sqlite:///tmp/runn-test.db
vars:
testEmail: "alice@example.com"
steps:
setup-db:
db:
testdb:
query: |
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
insert-user:
db:
testdb:
query: |
INSERT INTO users (name, email)
VALUES ('Alice', '{{ vars.testEmail }}')
check-user:
db:
testdb:
query: |
SELECT id, name, email, created_at
FROM users
WHERE email = $1
params:
- "{{ vars.testEmail }}"
test: |
len(steps.check_user.rows) == 1 &&
steps.check_user.rows[0].name == "Alice"
HTTPもデータベースも、同じtest
構文でアサーションを記述できます。
実践的なシナリオ例¶
CRUD操作の実装¶
完全なCRUD操作を実装した例です。
desc: ブログ記事のCRUD操作をテスト
runners:
blog: http://localhost:8080/api
vars:
authorId: "author-123"
steps:
# 1. 記事を作成
create_post:
blog:
/posts:
post:
body:
application/json:
title: "テスト記事"
content: "これはテスト記事です"
authorId: "{{ vars.authorId }}"
test: |
steps.create_post.res.status == 201 &&
steps.create_post.res.body.id != null
# 2. 作成した記事を取得
get_post:
blog:
/posts/{{ steps.create_post.res.body.id }}:
get: {}
test: |
steps.get_post.res.status == 200 &&
steps.get_post.res.body.title == "テスト記事"
# 3. 記事を更新
update_post:
blog:
/posts/{{ steps.create_post.res.body.id }}:
put:
body:
application/json:
title: "更新されたテスト記事"
content: "内容も更新しました"
test: steps.update_post.res.status == 200
# 4. 更新を確認
verify_update:
blog:
/posts/{{ steps.create_post.res.body.id }}:
get: {}
test: |
steps.verify_update.res.body.title == "更新されたテスト記事"
# 5. 記事を削除
delete_post:
blog:
/posts/{{ steps.create_post.res.body.id }}:
delete: {}
test: steps.delete_post.res.status == 204
# 6. 削除を確認
verify_delete:
blog:
/posts/{{ steps.create_post.res.body.id }}:
get: {}
test: steps.verify_delete.res.status == 404
1 scenario, 0 skipped, 0 failures
この例では以下を実装しています:
- Create → Read → Update → Delete のフロー
- ステップ間でのID受け渡し
- 削除後の404確認
YAML記述のテクニック¶
テクニック1: 複数行文字列¶
長いテキストの記述方法です。
runners:
httpbin: http://localhost:8080
steps:
- desc: |
これは複数行の
説明文です
httpbin:
/post:
post:
body:
text/plain: |
複数行の
テキストデータ
テクニック2: アンカー&エイリアス¶
共通設定を再利用する方法です。
# 共通のヘッダーを定義
commonHeaders: &headers
Content-Type: application/json
X-API-Version: "1.0"
runners:
blog: http://localhost:8080
steps:
- blog:
/users:
get:
headers:
<<: *headers # 共通ヘッダーを使用
Authorization: "Bearer token123"
共通設定を一箇所で管理することで、メンテナンスが容易になります。
テクニック3: 環境別設定¶
開発・本番環境の設定を切り替える方法です。
vars:
baseURL: ${BASE_URL:-http://localhost:8080}
apiKey: ${API_KEY:-test-api-key}
environment: ${ENV:-development}
# 環境別の設定をマップで管理
config:
development:
timeout: 60
retries: 3
production:
timeout: 30
retries: 1
runners:
blog: "{{ vars.baseURL }}"
steps:
test_request:
blog:
/test:
get:
headers:
X-API-Key: "{{ vars.apiKey }}"
test: current.res.status == 200