ストーリー
高橋アーキテクトがターミナルを開いた。
k6 の基本
最小構成のテスト
// basic-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
// テスト設定
export const options = {
vus: 10, // 仮想ユーザー数
duration: '30s', // テスト時間
};
// テスト本体(各VUが繰り返し実行)
export default function () {
const res = http.get('http://localhost:3000/api/products');
// レスポンスの検証
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1); // 思考時間(1秒待機)
}
実行方法
# 基本実行
k6 run basic-test.js
# 出力例
# ✓ status is 200.......... 100.00%
# ✓ response time < 500ms.. 98.50%
#
# http_req_duration.....: avg=45ms min=12ms med=38ms max=380ms p(90)=82ms p(95)=120ms
# http_reqs.............: 2850 95.0/s
ステージ(段階的負荷)
// staged-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 50 }, // 2分かけて50VUまで増加
{ duration: '5m', target: 50 }, // 5分間50VUを維持
{ duration: '2m', target: 100 }, // 2分かけて100VUまで増加
{ duration: '5m', target: 100 }, // 5分間100VUを維持
{ duration: '2m', target: 0 }, // 2分かけて0VUまで減少
],
thresholds: {
// 成功基準
http_req_duration: ['p(95)<300', 'p(99)<500'], // p95 < 300ms, p99 < 500ms
http_req_failed: ['rate<0.01'], // エラー率 < 1%
},
};
export default function () {
const res = http.get('http://localhost:3000/api/products');
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(Math.random() * 3 + 1); // 1-4秒のランダムな思考時間
}
実践的なシナリオ
// scenario-test.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { SharedArray } from 'k6/data';
// テストデータの読み込み
const productIds = new SharedArray('productIds', function () {
return JSON.parse(open('./test-data/product-ids.json'));
});
export const options = {
scenarios: {
// シナリオ1: 閲覧ユーザー(60%)
browsers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 60 },
{ duration: '5m', target: 60 },
{ duration: '1m', target: 0 },
],
exec: 'browsingScenario',
},
// シナリオ2: 購入ユーザー(15%)
buyers: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 15 },
{ duration: '5m', target: 15 },
{ duration: '1m', target: 0 },
],
exec: 'purchaseScenario',
},
},
thresholds: {
http_req_duration: ['p(95)<300'],
http_req_failed: ['rate<0.01'],
},
};
// 閲覧シナリオ
export function browsingScenario() {
group('Browse Products', function () {
// 商品一覧を閲覧
const listRes = http.get('http://localhost:3000/api/products');
check(listRes, { 'list status 200': (r) => r.status === 200 });
sleep(2);
// ランダムな商品を閲覧
const randomId = productIds[Math.floor(Math.random() * productIds.length)];
const detailRes = http.get(`http://localhost:3000/api/products/${randomId}`);
check(detailRes, { 'detail status 200': (r) => r.status === 200 });
sleep(3);
});
}
// 購入シナリオ
export function purchaseScenario() {
group('Purchase Flow', function () {
const randomId = productIds[Math.floor(Math.random() * productIds.length)];
// 商品詳細を閲覧
http.get(`http://localhost:3000/api/products/${randomId}`);
sleep(2);
// カートに追加
const cartRes = http.post(
'http://localhost:3000/api/cart',
JSON.stringify({ productId: randomId, quantity: 1 }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(cartRes, { 'cart add status 200': (r) => r.status === 200 });
sleep(1);
// 注文確定
const orderRes = http.post(
'http://localhost:3000/api/orders',
JSON.stringify({ cartId: cartRes.json('cartId') }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(orderRes, { 'order status 201': (r) => r.status === 201 });
sleep(1);
});
}
k6 の主要メトリクス
| メトリクス | 説明 | 目標値の例 |
|---|---|---|
http_req_duration | リクエスト全体の所要時間 | p95 < 300ms |
http_req_waiting | サーバー処理時間(TTFB) | p95 < 200ms |
http_req_failed | 失敗したリクエストの割合 | < 0.1% |
http_reqs | 1秒あたりのリクエスト数 | > 3000 RPS |
vus | アクティブな仮想ユーザー数 | - |
iterations | テスト反復の完了数 | - |
まとめ
| ポイント | 内容 |
|---|---|
| k6 | JavaScriptで書ける開発者フレンドリーな負荷テストツール |
| stages | 段階的に負荷を増減できる |
| scenarios | 複数のユーザー行動パターンを並行実行 |
| thresholds | 自動的に合格/不合格を判定 |
| check | レスポンスの正当性を検証 |
チェックリスト
- k6の基本的なテストを書ける
- stagesで段階的な負荷パターンを設定できる
- scenariosで複数のユーザー行動を定義できる
- thresholdsで成功基準を設定できる
次のステップへ
次は「結果分析とレポーティング」を学びます。テスト結果のp50、p95、p99の読み方と、ボトルネックの特定方法を理解しましょう。
推定読了時間: 30分