ストーリー
マイクロサービスのテストピラミッド
/\
/ \ E2E Tests(少数)
/ \ → 全体フロー確認
/──────\
/ \ Integration Tests
/ \ → サービス+DB, サービス+MQ
/────────────\
/ \ Contract Tests(重要!)
/ \ → サービス間の契約検証
/──────────────────\
Unit Tests(多数)
→ ビジネスロジック
Contract Testing(契約テスト)
サービス間のAPI契約が正しく守られていることを検証するテストです。
Consumer-Driven Contract Testing
// Consumer(利用側)が期待する契約を定義
// → Provider(提供側)がその契約を満たすことを検証
// 1. Consumer側: 期待する契約(Pact)を定義
// order-service が user-service に期待すること
const pact = new Pact({
consumer: "order-service",
provider: "user-service",
});
describe("User Service Contract", () => {
it("should return user by ID", async () => {
// 期待するリクエストとレスポンスを定義
await pact.addInteraction({
state: "user usr-123 exists",
uponReceiving: "a request for user usr-123",
withRequest: {
method: "GET",
path: "/users/usr-123",
headers: { Accept: "application/json" },
},
willRespondWith: {
status: 200,
headers: { "Content-Type": "application/json" },
body: {
id: like("usr-123"),
name: like("田中太郎"),
email: like("tanaka@example.com"),
// like() = 型と構造のみ検証、値は問わない
},
},
});
// Consumerのコードをモックサーバーに対して実行
const user = await userClient.getUser("usr-123");
expect(user.id).toBe("usr-123");
expect(user.name).toBeDefined();
});
});
// → Pactファイル(契約)が生成される
// 2. Provider側: 契約を検証
describe("User Service Provider Verification", () => {
it("should fulfill the contract with order-service", async () => {
const verifier = new Verifier({
provider: "user-service",
providerBaseUrl: "http://localhost:3000",
pactUrls: ["./pacts/order-service-user-service.json"],
stateHandlers: {
"user usr-123 exists": async () => {
// テスト用のデータをセットアップ
await seedUser({ id: "usr-123", name: "田中太郎" });
},
},
});
await verifier.verifyProvider();
// → すべての契約が満たされていることを確認
});
});
イベント契約テスト
// イベントの契約もテスト可能
describe("Order Created Event Contract", () => {
it("should produce valid order.created event", async () => {
// Producer側: イベントのスキーマを検証
const event = await orderService.createOrder(testData);
// CloudEvents仕様に準拠しているか
expect(event.specversion).toBe("1.0");
expect(event.type).toBe("com.example.order.created");
expect(event.data.orderId).toBeDefined();
expect(event.data.userId).toBeDefined();
expect(event.data.items).toBeInstanceOf(Array);
expect(event.data.totalAmount).toBeGreaterThan(0);
});
it("consumer should handle order.created event", async () => {
// Consumer側: イベントを正しく処理できるか
const event: OrderCreatedEvent = {
specversion: "1.0",
id: "evt-001",
source: "/order-service",
type: "com.example.order.created",
time: new Date().toISOString(),
data: {
orderId: "ord-001",
userId: "usr-001",
items: [{ productId: "prod-001", quantity: 2, unitPrice: 500 }],
totalAmount: 1000,
},
};
await paymentHandler.handleOrderCreated(event);
// → 決済処理が正しく開始されたことを検証
});
});
Chaos Engineering(カオスエンジニアリング)
意図的に障害を注入し、システムの耐障害性を検証するアプローチです。
基本的な原則
1. 定常状態を定義する(正常時のメトリクス)
2. 仮説を立てる(「サービスAが落ちても注文は処理される」)
3. 障害を注入する(サービスAを停止)
4. 結果を観察する(仮説と比較)
5. 改善する(発見した脆弱性を修正)
障害注入のパターン
// 障害注入の種類
const chaosExperiments = {
// 1. サービスの停止
serviceKill: {
action: "Payment Serviceの全インスタンスを停止",
hypothesis: "注文サービスがサーキットブレーカーで適切にフォールバック",
verify: "注文が『決済保留』状態で保存され、後で再処理される",
},
// 2. ネットワーク遅延
latencyInjection: {
action: "Inventory Serviceへの通信に3秒の遅延を追加",
hypothesis: "タイムアウト設定が適切に動作する",
verify: "3秒以内にタイムアウトし、リトライまたはフォールバック",
},
// 3. ネットワーク分断
networkPartition: {
action: "Order ServiceとPayment Service間の通信を遮断",
hypothesis: "イベントがメッセージキューにバッファリングされる",
verify: "通信復旧後にイベントが処理される",
},
// 4. リソース枯渇
resourceExhaustion: {
action: "サービスのCPU使用率を90%に上げる",
hypothesis: "オートスケーリングが発動する",
verify: "2分以内に新しいインスタンスが追加される",
},
// 5. データ破損
dataCorruption: {
action: "Read Modelのデータを一部削除",
hypothesis: "リビルドプロセスで復旧できる",
verify: "イベントストアからRead Modelが再構築される",
},
};
主要なツール
| ツール | プラットフォーム | 特徴 |
|---|---|---|
| Chaos Monkey | Netflix OSS | ランダムにインスタンスを停止 |
| Litmus | Kubernetes | K8s向けカオス実験 |
| AWS Fault Injection Simulator | AWS | マネージドカオス |
| Gremlin | マルチプラットフォーム | 商用、高機能 |
| Toxiproxy | 汎用 | ネットワーク障害シミュレーション |
オブザーバビリティ
テストの結果を観察するための3本柱です。
// 分散トレーシング: リクエストの流れを追跡
const tracing = {
tool: "OpenTelemetry + Jaeger",
purpose: "1つのリクエストが複数サービスを通過する流れを可視化",
example: "注文API → 決済サービス → 在庫サービス → 通知サービス",
};
// メトリクス: 数値指標の収集
const metrics = {
tool: "Prometheus + Grafana",
purpose: "レイテンシ、エラー率、スループットを監視",
goldSignals: ["Latency", "Traffic", "Errors", "Saturation"],
};
// ログ: 構造化ログの集約
const logging = {
tool: "ELK Stack (Elasticsearch + Logstash + Kibana)",
purpose: "全サービスのログを集約して検索",
key: "correlationIdで関連ログを紐づけ",
};
まとめ
| ポイント | 内容 |
|---|---|
| Contract Testing | サービス間のAPI/イベント契約を検証 |
| Chaos Engineering | 意図的に障害を注入して耐障害性を検証 |
| オブザーバビリティ | トレーシング、メトリクス、ログの3本柱 |
| テストの階層 | Unit → Contract → Integration → E2E |
チェックリスト
- Consumer-Driven Contract Testingの仕組みを説明できる
- Chaos Engineeringの5つのステップを理解した
- 障害注入のパターンを3つ以上挙げられる
- オブザーバビリティの3本柱を説明できる
次のステップへ
次は演習で、CQRSシステムの設計と結果整合性の戦略を実践してみましょう。
推定読了時間: 30分