第6章:高度な機能編 - runnの真の力を解放せよ!¶
ここからが本番だ! 今まで学んできた機能はほんの序の口に過ぎない。この章では、runnの本当にすごい機能を伝授しよう!これらの機能をマスターすれば、どんなに複雑なテストシナリオも余裕だ!
🔁 ループ処理 - 繰り返しの魔法!¶
🎯 基本的なループ - 単純な繰り返しを極めろ!¶
desc: 基本的なループ処理
runners:
api: https://api.example.com
vars:
test_users:
- name: "Alice"
email: "alice@example.com"
- name: "Bob"
email: "bob@example.com"
- name: "Charlie"
email: "charlie@example.com"
steps:
# 単純な回数指定ループ
simple_loop:
loop: 5
req:
api:///api/ping:
get:
test: current.res.status == 200
dump:
iteration: i # ループインデックス(0から開始)
# 配列の各要素に対するループ
array_loop:
loop:
count: len(vars.test_users)
req:
api:///users:
post:
body:
application/json:
name: "{{ vars.test_users[i].name }}"
email: "{{ vars.test_users[i].email }}"
test: current.res.status == 201
🔄 条件付きループ(リトライ機能) - 諸めないテストの極意!¶
desc: 条件付きループ(リトライ機能)
runners:
api: https://api.example.com
vars:
operation_id: "op123"
steps:
# 成功するまでリトライ
retry_until_success:
loop:
count: 10 # 最大10回
until: current.res.status == 200 # 成功条件
minInterval: 1 # 最小間隔(秒)
maxInterval: 5 # 最大間隔(秒)
req:
api:///unstable-endpoint:
get:
test: current.res.status == 200
# 複雑な条件でのリトライ
complex_retry:
loop:
count: 5
until: |
current.res.status == 200 &&
current.res.body.status == "ready" &&
len(current.res.body.items) > 0
minInterval: 2
maxInterval: 10
req:
api:///async-operation/{{ vars.operation_id }}/status:
get:
test: |
current.res.body.status == "ready"
# エラー条件でのループ終了
stop_on_error:
loop:
count: 100
until: current.res.status >= 400 # エラーが発生したら停止
req:
api:///batch-process:
post:
body:
application/json:
batch_id: "{{ i }}"
test: |
current.res.status < 400 || # 成功
(current.res.status >= 400 && i > 0) # エラーだが少なくとも1回は成功
🎮 動的なループ制御 - ループを思いのままに操れ!¶
desc: 動的なループ制御
runners:
api: https://api.example.com
vars:
page_size: 10
max_pages: 100
items_to_process:
- id: 1
name: "Item 1"
needs_processing: true
- id: 2
name: "Item 2"
needs_processing: false
- id: 3
name: "Item 3"
needs_processing: true
steps:
# ページネーションを使った全データ取得
paginated_fetch:
loop:
count: vars.max_pages
until: len(current.res.body.data) < vars.page_size # 最後のページに到達
req:
api:///users:
get:
query:
page: "{{ i + 1 }}"
limit: "{{ vars.page_size }}"
test: current.res.status == 200
dump:
page_number: i + 1
items_count: len(current.res.body.data)
total_fetched: |
sum(map(steps.paginated_fetch, {len(.res.body.data)}))
# 条件に基づく動的ループ
conditional_processing:
loop:
count: len(vars.items_to_process)
if: vars.items_to_process[i].needs_processing
req:
api:///process:
post:
body:
application/json: "{{ vars.items_to_process[i] }}"
test: current.res.status == 200
🔀 条件付き実行 - 賢いテストの秘訣!¶
🎆 基本的な条件分岐 - if文でテストを制御!¶
desc: 基本的な条件分岐
runners:
api: https://api.example.com
vars:
enable_new_feature: true
steps:
# 環境による条件分岐
environment_specific:
if: env.ENVIRONMENT == "production"
req:
api:///production-only-endpoint:
get:
test: current.res.status == 200
# 変数による条件分岐
feature_flag_check:
if: vars.enable_new_feature
req:
api:///new-feature:
get:
test: current.res.status == 200
# ユーザー作成ステップ(常に実行)
user_creation:
req:
api:///users:
post:
body:
application/json:
name: "Test User"
email: "test@example.com"
test: |
current.res.status == 201 || current.res.status == 409
# 前のステップの結果による条件分岐
conditional_on_previous:
if: steps.user_creation.res.status == 201
req:
api:///users/{{ steps.user_creation.res.body.id }}/activate:
post:
test: current.res.status == 200
🧠 複雑な条件式 - どんな条件も表現できる!¶
desc: 複雑な条件式
runners:
api: https://api.example.com
vars:
user_role: "admin"
user_permissions:
- read
- write
- delete
api_version: 2
maintenance_end: "2024-01-01T00:00:00Z"
test_data:
- id: 1
name: "Test Data 1"
resource_data:
name: "Protected Resource"
type: "document"
steps:
complex_conditions:
if: |
(env.ENVIRONMENT == "test" || env.ENVIRONMENT == "staging") &&
vars.user_role == "admin" &&
len(vars.test_data) > 0
req:
api:///admin/test-data:
post:
body:
application/json: "{{ vars.test_data }}"
test: current.res.status == 201
# 複数条件の組み合わせ
multi_condition_check:
if: |
vars.api_version >= 2 &&
(vars.user_permissions contains "write" || vars.user_role == "admin") &&
time.now() > time.parse(vars.maintenance_end, time.RFC3339)
req:
api:///v2/protected-resource:
post:
body:
application/json:
action: "create"
data: "{{ vars.resource_data }}"
test: current.res.status in [200, 201]
🛡️ エラーハンドリングと条件分岐 - 失敗を成功に変えろ!¶
desc: エラーハンドリングと条件分岐
runners:
api: https://api.example.com
steps:
# エラー時の代替処理
primary_request:
req:
api:///primary-endpoint:
get:
test: true # エラーでも続行
fallback_request:
if: steps.primary_request.res.status != 200
req:
api:///fallback-endpoint:
get:
test: current.res.status == 200
# 成功時の追加処理
success_processing:
if: |
steps.primary_request.res.status == 200 ||
steps.fallback_request.res.status == 200
req:
api:///process-result:
post:
body:
application/json:
source: |
steps.primary_request.res.status == 200 ? "primary" : "fallback"
data: |
steps.primary_request.res.status == 200 ?
steps.primary_request.res.body :
steps.fallback_request.res.body
test: current.res.status == 200
📦 シナリオのインクルード - DRYの極意!¶
🔗 基本的なインクルード - シナリオを再利用せよ!¶
# main.yml
desc: メインシナリオ
vars:
base_url: https://api.example.com
user_id: 123
test_username: "testuser"
test_password: "testpass123"
steps:
# 共通の認証処理をインクルード
- include:
path: ./common/auth.yml
vars:
username: "{{ vars.test_username }}"
password: "{{ vars.test_password }}"
# ユーザー操作をインクルード
- include:
path: ./user/user_operations.yml
vars:
user_id: "{{ vars.user_id }}"
auth_token: "{{ steps[0].auth_token }}"
# クリーンアップ処理をインクルード
- include:
path: ./common/cleanup.yml
# common/auth.yml
desc: 認証処理
steps:
login:
req:
"{{ parent.vars.base_url }}/auth/login":
post:
body:
application/json:
username: "{{ vars.username }}"
password: "{{ vars.password }}"
test: current.res.status == 200
dump:
auth_token: current.res.body.token
🎭 動的なインクルード - 実行時にシナリオを選択!¶
desc: 動的なインクルード
vars:
test_type: "integration"
integration_data:
db_name: "test_db"
test_user: "test_user"
test_scenarios:
- path: "./scenarios/user_test.yml"
data:
username: "alice"
- path: "./scenarios/product_test.yml"
data:
product_id: 123
- path: "./scenarios/order_test.yml"
data:
order_id: 456
steps:
# 条件に基づくインクルード
conditional_include:
if: vars.test_type == "integration"
include:
path: ./integration/full_test.yml
vars:
test_data: "{{ vars.integration_data }}"
# ループ内でのインクルード
multiple_scenarios:
loop:
count: len(vars.test_scenarios)
include:
path: "{{ vars.test_scenarios[i].path }}"
vars:
scenario_data: "{{ vars.test_scenarios[i].data }}"
iteration: "{{ i }}"
🏢 ネストしたインクルード - 階層的なシナリオ構築!¶
# level1.yml
desc: レベル1のシナリオ
steps:
setup:
include:
path: ./setup/database.yml
vars:
db_name: "test_{{ time.unix(time.now()) }}"
main_test:
include:
path: ./level2.yml
vars:
db_name: "{{ steps.setup.db_name }}"
# level2.yml
desc: レベル2のシナリオ
steps:
data_preparation:
include:
path: ./data/prepare.yml
vars:
target_db: "{{ parent.vars.db_name }}"
execute_test:
include:
path: ./level3.yml
vars:
prepared_data: "{{ steps.data_preparation.result }}"
⚡ 並行実行制御 - スピードの限界を突破!¶
🚀 基本的な並行実行 - 複数テストを同時実行!¶
desc: 並行実行の制御
# 同時実行数の制限
concurrency: 5
runners:
api: https://api.example.com
vars:
api_endpoints:
- "/api/health"
- "/api/status"
- "/api/version"
- "/api/ping"
- "/api/ready"
- "/api/metrics"
- "/api/info"
- "/api/check"
steps:
# 複数のAPIエンドポイントを並行テスト
parallel_api_tests:
loop:
count: len(vars.api_endpoints)
req:
"{{ vars.api_endpoints[i] }}":
get:
test: current.res.status == 200
🔒 共有リソースの制御 - 競合を防ぐ鉄壁の守り!¶
desc: 共有リソースの制御
# データベースを使用するテストは同時に1つだけ実行
concurrency: use-database
runners:
db: postgres://user:pass@localhost:5432/testdb?sslmode=disable
steps:
database_test:
db:
db:///:
query: |
INSERT INTO test_table (data) VALUES ($1)
params:
- "{{ faker.randomString(10) }}"
test: current.rowsAffected == 1
🎆 複雑な並行制御 - プロフェッショナルの技!¶
desc: 複雑な並行実行制御
vars:
worker_count: 10
batch_size: 100
steps:
# ワーカープロセスのシミュレーション
worker_simulation:
loop:
count: vars.worker_count
include:
path: ./worker/process_batch.yml
vars:
worker_id: "{{ i }}"
batch_start: "{{ i * vars.batch_size }}"
batch_end: "{{ (i + 1) * vars.batch_size }}"
# 結果の集約
aggregate_results:
dump:
total_processed: |
sum(map(steps.worker_simulation, {.processed_count}))
success_rate: |
sum(map(steps.worker_simulation, {.success_count})) /
sum(map(steps.worker_simulation, {.processed_count}))
🌐 依存関係の定義 - テストの流れを完全制御!¶
🔗 基本的な依存関係 - 順番を守る賢いテスト!¶
desc: 依存関係のあるテスト
needs:
setup: ./setup/environment.yml
data: ./setup/test_data.yml
runners:
api: https://api.example.com
steps:
main_test:
req:
api:///api/test:
get:
headers:
Authorization: "Bearer {{ needs.setup.auth_token }}"
test: current.res.status == 200
data_validation:
test: |
current.res.body.count == needs.data.expected_count
🕸️ 複雑な依存関係 - ネットワーク状の依存も余裕!¶
desc: 複雑な依存関係の管理
needs:
# 基本セットアップ
base_setup: ./setup/base.yml
# データベースセットアップ(base_setupに依存)
db_setup:
path: ./setup/database.yml
needs:
- base_setup
# ユーザーデータ準備(db_setupに依存)
user_data:
path: ./data/users.yml
needs:
- db_setup
# 商品データ準備(db_setupに依存)
product_data:
path: ./data/products.yml
needs:
- db_setup
runners:
api: https://api.example.com
steps:
integration_test:
req:
api:///api/orders:
post:
body:
application/json:
user_id: "{{ needs.user_data.test_user_id }}"
product_id: "{{ needs.product_data.test_product_id }}"
quantity: 1
test: current.res.status == 201
🔧 カスタムランナーの作成 - 独自のランナーを生み出せ!¶
🔌 プラグインランナーの定義 - runnを拡張せよ!¶
desc: カスタムランナーの使用例
runners:
# カスタムHTTPクライアント
custom_http:
type: http
base_url: https://api.example.com
default_headers:
User-Agent: "MyApp/1.0"
Accept: "application/json"
timeout: 30s
retry_count: 3
# カスタムデータベース接続
custom_db:
type: db
dsn: "{{ env.DATABASE_URL }}"
max_connections: 10
connection_timeout: 5s
steps:
custom_request:
req:
custom_http:///users:
get:
headers:
X-Custom-Header: "custom-value"
test: current.res.status == 200
🌍 外部コマンドランナー - どんなツールも統合!¶
desc: 外部コマンドランナー
vars:
namespace: "default"
project_root: "/app"
image_name: "myapp"
tag: "latest"
runners:
# 外部ツールの実行
kubectl:
type: exec
command: kubectl
default_args:
- --kubeconfig={{ env.KUBECONFIG }}
- --namespace={{ vars.namespace }}
# Docker操作
docker:
type: exec
command: docker
working_dir: "{{ vars.project_root }}"
steps:
k8s_deployment:
exec:
kubectl:///:
args:
- apply
- -f
- deployment.yaml
test: current.exit_code == 0
docker_build:
exec:
docker:///:
args:
- build
- -t
- "{{ vars.image_name }}:{{ vars.tag }}"
- .
test: current.exit_code == 0
🎭 高度なデータ処理 - データを魔法のように操れ!¶
🎲 動的なテストデータ生成 - 無限のテストデータ!¶
desc: 動的なテストデータ生成
runners:
api: https://api.example.com
vars:
# 動的なテストデータ生成
test_users: |
map(range(1, 11), {
"id": .,
"name": faker.name(),
"email": faker.email(),
"age": faker.randomInt(18, 65),
"department": faker.randomChoice(["IT", "Sales", "Marketing", "HR"]),
"salary": faker.randomInt(30000, 100000),
"active": faker.randomBool()
})
steps:
bulk_user_creation:
loop:
count: len(vars.test_users)
req:
api:///users:
post:
body:
application/json: "{{ vars.test_users[i] }}"
test: current.res.status == 201
# 生成されたデータの統計分析
data_analysis:
dump:
total_users: len(vars.test_users)
active_users: len(filter(vars.test_users, {.active}))
avg_age: |
sum(map(vars.test_users, {.age})) / len(vars.test_users)
departments: |
groupBy(vars.test_users, {.department})
salary_stats:
min: min(map(vars.test_users, {.salary}))
max: max(map(vars.test_users, {.salary}))
avg: |
sum(map(vars.test_users, {.salary})) / len(vars.test_users)
🌀 複雑なデータ変換 - データの形を自在に変えろ!¶
desc: 複雑なデータ変換
runners:
api: https://api.example.com
steps:
data_transformation:
req:
api:///api/raw-data:
get:
dump:
# 生データの変換
transformed_data: |
map(current.res.body.items, {
merge(
pick(., ["id", "name", "created_at"]),
{
"display_name": upper(.name),
"age_days": time.sub(time.now(), time.parse(.created_at, time.RFC3339)) / time.day,
"category": .price > 1000 ? "premium" : "standard",
"tags": split(.tag_string, ","),
"metadata": fromJSON(.metadata_json ?? "{}")
}
)
})
# 集約データの生成
summary: |
let items = current.res.body.items;
{
"total_count": len(items),
"premium_count": len(filter(items, {.price > 1000})),
"categories": unique(map(items, {.category})),
"price_ranges": groupBy(items, {
.price < 100 ? "low" :
.price < 1000 ? "medium" : "high"
}),
"recent_items": filter(items, {
time.sub(time.now(), time.parse(.created_at, time.RFC3339)) < time.day * 7
})
}
🛡️ エラーハンドリングとデバッグ - トラブルシューティングの達人!¶
💜 包括的なエラーハンドリング - どんなエラーも恐れない!¶
desc: 包括的なエラーハンドリング
runners:
api: https://api.example.com
vars:
resource_id: "res123"
steps:
robust_api_call:
loop:
count: 3
until: |
current.res.status == 200 ||
current.res.status == 404 # 404は正常な結果として扱う
minInterval: 1
maxInterval: 5
req:
api:///api/resource/{{ vars.resource_id }}:
get:
test: |
current.res.status in [200, 404]
dump:
# エラー情報の詳細記録
error_info: |
current.res.status >= 400 ? {
"status": current.res.status,
"error": current.res.body.error ?? "Unknown error",
"timestamp": time.now(),
"attempt": i + 1
} : null
🔍 デバッグ情報の出力 - 問題を瞬時に特定!¶
desc: デバッグ情報の出力
runners:
api: https://api.example.com
vars:
complex_data:
operation: "create"
resource: "user"
data:
name: "Test User"
email: "test@example.com"
steps:
debug_step:
req:
api:///api/complex-operation:
post:
body:
application/json: "{{ vars.complex_data }}"
# 詳細なデバッグ情報
dump:
request_info:
url: current.req.url
method: current.req.method
headers: current.req.headers
body_size: len(toJSON(current.req.body))
response_info:
status: current.res.status
headers: current.res.headers
body_size: len(toJSON(current.res.body))
response_time: current.res.response_time
validation_details:
expected_fields: ["id", "name", "status"]
actual_fields: keys(current.res.body)
missing_fields: |
filter(["id", "name", "status"], {
!(. in keys(current.res.body))
})
extra_fields: |
filter(keys(current.res.body), {
!(. in ["id", "name", "status"])
})
🎆 まとめ - 高度な機能のマスター誕生!¶
やったぞ! あなたは今、runnの真の力を解放した!
🏆 この章で手に入れた8つの超能力:¶
- 🔁 ループ処理: 繰り返しを完全に支配!リトライも思いのまま!
- 🔀 条件付き実行: 賢いテストが自分で判断!
- 📦 シナリオのインクルード: DRY原則を極限まで追求!
- ⚡ 並行実行制御: スピードの限界を突破!
- 🌐 依存関係の定義: 複雑なフローも美しく制御!
- 🔧 カスタムランナー: あなただけの特別なランナーを作れ!
- 🎭 高度なデータ処理: データを魔法のように変形!
- 🛡️ エラーハンドリング: どんなエラーも恐れない!
これらの機能を絶妙に組み合わせれば、あなたのテストは芸術作品に昇華する。もう、どんなに複雑なプロダクション環境も完璧にテストできる!
次章では、runnの最終兵器、Goテストヘルパーとしての使い方を伝授しよう! これであなたも真のrunnマスターだ!