api-testing-patterns について
api-testing-patternsは、テスト担当者やAPI開発チーム向けの、APIテスト戦略とパターンのガイドです。契約・コンポーネント・統合テストを扱い、利用側の視点から動作やエラー処理を検証します。
主な機能
- 01契約・コンポーネント・統合テストの選択
- 02利用側の視点でのAPI契約検証
- 03認証・入力・エラーケースの確認
- 04冪等性と並行処理のテスト
- 05OpenAPI仕様からのテスト生成
活用シーン
- 01RESTまたはGraphQL APIのテスト戦略設計
- 02マイクロサービス間の利用側契約の検証
- 03CI/CDでのAPI変更による互換性破壊の防止
- 04重要なエンドポイントの負荷特性確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ proffesor-for-testing/ agentic-qe --skill api-testing-patternsインストール後、Claude Code で/api-testing-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
config.jsonevals/api-testing-patterns.yamlschemas/output.jsonscripts/validate-config.jsontemplates/api-test-scaffold.md詳細
API テストのパターン
<default_to_action> API をテストする場合、または API テスト戦略を設計する場合:
- テストレベルを特定します:コントラクト、統合、コンポーネントのいずれか
- 実装ではなくコントラクトをテストします(コンシューマーの視点)
- 認証、入力、エラー、冪等性、並行性を検証します
- スキーマ検証を使って CI/CD で自動化します
- 本番 API を監視してコントラクトのドリフトを検出します
パターンのクイック選択:
- マイクロサービス → コンシューマー主導のコントラクト(Pact)
- REST API → CRUD + ページネーション + フィルタリングのテスト
- GraphQL → クエリの検証 + 複雑度の制限
- 外部依存関係 → コンポーネントテストでモックを使用
- パフォーマンス → 重要なエンドポイントの負荷テスト
重要な成功要因:
- API はコントラクトです。コンシューマーの視点からテストします
- 正常系だけでなく、必ずエラーシナリオもテストします
- 破壊的変更を防ぐため、API テストにバージョンを付けます </default_to_action>
クイックリファレンスカード
使用する場面
- REST または GraphQL API のテスト
- マイクロサービスのコントラクトの検証
- API テスト戦略の設計
- API の破壊的変更の防止
テストレベル
| レベル | 目的 | 依存関係 | 速度 |
|---|---|---|---|
| コントラクト | プロバイダーとコンシューマー間の合意 | なし | 高速 |
| コンポーネント | 分離した状態の API | モック | 高速 |
| 統合 | 実際の依存関係 | データベース、サービス | 低速 |
重要なテストシナリオ
| シナリオ | 必須テスト | 例 |
|---|---|---|
| 認証 | 401/403 の処理 | 期限切れのトークン、誤ったユーザー |
| 入力 | 400 の検証 | 必須フィールドの欠落、誤った型 |
| エラー | 500 の適切な処理 | DB の停止、タイムアウト |
| 冪等性 | 重複の防止 | 同じ冪等性キー |
| 並行性 | 競合状態 | 並列チェックアウト |
ツール
- コントラクト:Pact、Spring Cloud Contract
- REST:Supertest、REST-assured、Playwright
- 負荷:k6、Artillery、JMeter
エージェントの連携
qe-api-contract-validator:コントラクトを検証し、破壊的変更を検出しますqe-test-generator:OpenAPI 仕様からテストを生成しますqe-performance-tester:エンドポイントの負荷テストを実行しますqe-security-scanner:API のセキュリティテストを実行します
コントラクトテスト
パターン:コンシューマー主導のコントラクト
// Consumer defines expectations
const contract = {
request: { method: 'POST', path: '/orders', body: { productId: 'abc', quantity: 2 } },
response: { status: 201, body: { orderId: 'string', total: 'number' } }
};
// Provider must fulfill
test('order API meets contract', async () => {
const response = await api.post('/orders', { productId: 'abc', quantity: 2 });
expect(response.status).toBe(201);
expect(response.body).toMatchSchema({
orderId: expect.any(String),
total: expect.any(Number)
});
});
**使用場面:**マイクロサービス、分散システム、サードパーティとの統合
重要なテストパターン
認証と認可
describe('Auth', () => {
it('rejects without token', async () => {
expect((await api.get('/orders')).status).toBe(401);
});
it('rejects expired token', async () => {
const expired = generateExpiredToken();
expect((await api.get('/orders', { headers: { Authorization: `Bearer ${expired}` } })).status).toBe(401);
});
it('blocks cross-user access', async () => {
const userAToken = generateToken({ userId: 'A' });
expect((await api.get('/orders/user-B-order', { headers: { Authorization: `Bearer ${userAToken}` } })).status).toBe(403);
});
});
入力検証
describe('Validation', () => {
it('validates required fields', async () => {
const response = await api.post('/orders', { quantity: 2 }); // Missing productId
expect(response.status).toBe(400);
expect(response.body.errors).toContain('productId is required');
});
it('validates types', async () => {
expect((await api.post('/orders', { productId: 'abc', quantity: 'two' })).status).toBe(400);
});
it('validates ranges', async () => {
expect((await api.post('/orders', { productId: 'abc', quantity: -5 })).status).toBe(400);
});
});
冪等性
it('prevents duplicates with idempotency key', async () => {
const key = 'unique-123';
const data = { productId: 'abc', quantity: 2 };
const r1 = await api.post('/orders', data, { headers: { 'Idempotency-Key': key } });
const r2 = await api.post('/orders', data, { headers: { 'Idempotency-Key': key } });
expect(r1.body.orderId).toBe(r2.body.orderId); // Same order
});
並行性
it('handles race condition on inventory', async () => {
const promises = Array(10).fill().map(() =>
api.post('/orders', { productId: 'abc', quantity: 1 })
);
const responses = await Promise.all(promises);
const successful = responses.filter(r => r.status === 201);
const inventory = await db.inventory.findById('abc');
expect(inventory.quantity).toBe(initialQuantity - successful.length);
});
REST CRUD パターン
describe('Product CRUD', () => {
let productId;
it('CREATE', async () => {
const r = await api.post('/products', { name: 'Widget', price: 10 });
expect(r.status).toBe(201);
productId = r.body.id;
});
it('READ', async () => {
const r = await api.get(`/products/${productId}`);
expect(r.body.name).toBe('Widget');
});
it('UPDATE', async () => {
const r = await api.put(`/products/${productId}`, { price: 12 });
expect(r.body.price).toBe(12);
});
it('DELETE', async () => {
expect((await api.delete(`/products/${productId}`)).status).toBe(204);
expect((await api.get(`/products/${productId}`)).status).toBe(404);
});
});
ベストプラクティス
✅ 実施すること
- コンシューマーの視点からテストする
- スキーマ検証を使用する(完全一致する値ではなく)
- エラーシナリオを徹底的にテストする
- API テストにバージョンを付ける
- CI/CD で自動化する
❌ 避けること
- コントラクトではなく実装をテストする
- HTTP のセマンティクス(ステータスコード)を無視する
- ネガティブテストを行わない
- フィールドの順序や余分なフィールドをアサートする
- テストが遅くなること(外部サービスをモックする)
エージェント支援による API テスト
// Validate contracts
await Task("Contract Validation", {
spec: 'openapi.yaml',
endpoint: '/orders',
checkBreakingChanges: true
}, "qe-api-contract-validator");
// Generate tests from spec
await Task("Generate API Tests", {
spec: 'openapi.yaml',
coverage: 'comprehensive',
include: ['happy-paths', 'input-validation', 'auth-scenarios', 'error-handling']
}, "qe-test-generator");
// Load test
await Task("API Load Test", {
endpoint: '/orders',
rps: 1000,
duration: '5min'
}, "qe-performance-tester");
// Security scan
await Task("API Security Scan", {
spec: 'openapi.yaml',
checks: ['sql-injection', 'xss', 'broken-auth', 'rate-limiting']
}, "qe-security-scanner");
エージェント連携のヒント
メモリの名前空間
aqe/api-testing/
├── contracts/* - API contract definitions
├── generated-tests/* - Generated test suites
├── validation/* - Contract validation results
└── performance/* - Load test results
フリート連携
const apiFleet = await FleetManager.coordinate({
strategy: 'contract-testing',
agents: ['qe-api-contract-validator', 'qe-test-generator', 'qe-test-executor'],
topology: 'mesh'
});
await apiFleet.execute({
services: [
{ name: 'orders-api', consumers: ['checkout-ui', 'admin-api'] },
{ name: 'payment-api', consumers: ['orders-api'] }
]
});
関連スキル
- agentic-quality-engineering - エージェントを使った API テスト
- tdd-london-chicago - API テストのためのロンドン学派
- performance-testing - API の負荷テスト
- security-testing - API セキュリティの検証
- contract-testing - コンシューマー主導のコントラクトを詳しく解説
覚えておくこと
API テストとは、実装ではなくコントラクトと動作を検証することです。正しいレスポンス、適切なエラー処理、許容できるパフォーマンスなど、コンシューマーにとって重要な点に焦点を当てます。
**エージェントを活用する場合:**エージェントはコントラクトの検証を自動化し、仕様から包括的なテストスイートを生成し、本番 API のドリフトを監視します。大規模な API 品質の維持にエージェントを活用してください。
注意点
- エージェントは実際の API ではなく、ドキュメントに記載された API に対するテストを生成します。まず実行中のサービスで必ず検証してください
- 認証トークンはテスト実行の間に期限切れになります。有効期間の長いトークンを使うフィクスチャを使用するか、各テストスイートの実行前に更新してください
- CI でのレート制限により、断続的な失敗が発生します。429 レスポンスに対して指数バックオフを伴うリトライを追加してください
- GraphQL のイントロスペクションは本番環境で無効になっている場合があります。本番エンドポイントではなく、ステージング環境のスキーマに対してテストしてください
- 冪等性のテストでは、実行ごとに一意のリクエスト ID が必要です。ID をハードコードすると、リトライ時に誤ってテストが成功することがあります
コメント
Markdown 対応