精読「マイクロサービスアーキテクチャ 第2版」(第二部 実装 - 第9章 テスト)
マイクロサービスアーキテクチャ 第2版
マイクロサービスの設計、実装、運用に必要なベストプラクティスや最新技術を解説した、実践的なガイドブックです。これを読めば、マイクロサービスに関してそれっぽい会話もできますよ。
関連記事
テストの種類
Brian Marickの「テストの4象限」を基に、テストを技術面(性能テストや単体テストなど自動化が中心)とビジネス面(受け入れテストや探索的テストなど手動が中心)に分類。本番環境へのデプロイ前検証が主な目的。最近は手動テストを減らし、自動化を進める傾向があり、特にマイクロサービスでは効率的な自動化が必要とされている。
アジャイルテストの4象限とスクラムチームとしての活用方法より
テストスコープ
Mike Cohnの「テストピラミッド」モデル[1]は、自動テストを単体テスト、サービステスト、UIテスト(エンドツーエンドテスト)に分類し、それぞれの役割とスコープを示している。
ピラミッドの下部にあるテストほど速く、フィードバックサイクルが短縮され、特定の問題箇所を素早く修正できる。一方、上部のテストはスコープが広く、システム全体の信頼性を向上させる。
モデルの用語には曖昧さがあるものの、目的に応じた異なるスコープの自動テストが必要であり、チームのニーズに合った適切なテスト設計が重要。
テストピラミッドより
単体テスト
単一の関数やメソッドを外部依存を排除してテストする方法。主にテスト駆動開発(TDD)やプロパティベーステストで生成され、非常に高速かつ数多く実行されることが特徴。1分以内に数千のテストが可能で、コード変更時に自動実行することで迅速なフィードバックを得られる。技術面でのバグ検出に役立ち、小さなコード部分を分離してテストすることで、開発者を支援する。
サービステスト
UIを介さずマイクロサービスを直接テストします。外部依存をスタブ化してスコープを限定し、動作確認と問題特定を容易にする。単体テストより広い範囲をカバーしつつも、エンドツーエンドテストより高速で安定している。
エンドツーエンドテスト
システム全体を対象に実施するテストで、ブラウザ経由のGUI操作やファイルアップロードなどを模倣する。本番コードを広範囲にテストするため、通過すれば本番環境での動作に高い信頼を得られる。しかし、スコープが広いため、特にマイクロサービス環境では実施が困難になる場合がある。
トレードオフ
テストのスコープとフィードバック速度のバランスが重要。単体テストは小さく、迅速に実行できるため問題特定が早いが、スコープが広がると実行時間が長くなり、フィードバックが遅くなる。また、テストの記述やメンテナンスのコストも増加する。ピラミッド型でテスト数は下に行くほど多くなり、過剰なサービステストやエンドツーエンドテストはフィードバックに悪影響を与えるため、単体テストに置き換えることが推奨される。逆ピラミッド型(テストスノーコーン)はテストが遅くなり、ビルドの遅延や長期間の障害が発生しやすくなる。
アジャイルな3つのテスト分類を抑え、持続的なセーフティネットづくりを行おうより
サービステストの実装
マイクロサービスの機能をスライスして、そのサービスのみをテストする。例えば、顧客
サービスのテストでは、顧客
サービスをデプロイし、関連するロイヤリティ
サービスをスタブ化[2]して、テストの対象を限定する。自動ビルド後には、マイクロサービス用のバイナリ成果物(例:コンテナイメージ)が作成され、これを簡単にデプロイする。テストでは、下流のコラボレータをスタブ化し、テスト対象のサービスがスタブと接続するように設定し、実際のレスポンスを模倣したものを返すように構成する。
モックかスタブか
サービステストでは、下流のコラボレータをスタブ化し、定型のレスポンスを返すように設定する。例えば、顧客123の残高に対して15,000を返すような処理。一方、モックを使用することで、呼び出しが行われたかを確認し、予期した呼び出しがなければテストを失敗させることができる。モックは副作用を確実に発生させる場合に有用ですが、使いすぎるとテストが脆弱になる。
一般的には、サービステストではモックよりもスタブを使用することが多い。スタブとモックの違いは重要だが、ツールを使うと両方を実装することが可能。
スタブとは?ドライバ・モックとの違いや3つのメリット・注意点より
高度なスタブサービス
スタブサービスは通常、自分で開発するが、mountebankというツールを使うと、スタブ/モックサーバの設定が容易になる。mountebankは、HTTPやTCPなどのプロトコルを介してリクエストに応じたレスポンスを返すことができ、複数の下流サービスのスタブ化に役立つ。
(扱いにくい)エンドツーエンドテストの実装
マイクロサービスシステムのエンドツーエンドテストは、複数のサービスをまとめてデプロイして品質を確認しますが、時間がかかり障害診断が難しい。重複したテストを避けるため、サービス変更後に共有のエンドツーエンドテストを実行する「ファンイン」モデルが有効だが、依然として欠点がある。
信頼できない脆弱テスト
テストのスコープが広がると、テストの部品が増え、信頼性が低くなり、テストが不安定(フレーキーテスト)になることがある。フレーキーテストは、失敗しても再実行で通過する場合があり、これを放置するとテストスイートへの信頼が失われ、問題を見逃す原因となる。フレーキーテストを早期に発見し、修正または除外することが重要であり、テスト対象の環境を安定させることや、スコープを狭めてテストを改善することが推奨される。
誰がエンドツーエンドテストを書くか
エンドツーエンドテストの責任者が不明確だと、テストケースが増えたり、結果が無視されたりする。解決策として、特定のチームに責任を持たせるか、複数チームで共有する方法がありますが、どちらも課題がある。最適な方法は難しく、場合によっては共同所有が必要。
エンドツーエンドテストの実行期間
エンドツーエンドテストは時間がかかり、フレーキーなテストが問題となることがある。並列実行などで改善できるが、不要なテストを削除することが重要。しかし、テスト削除にはリスクが伴い、その必要性とリスクをバランスよく議論することが難しい。特に、同じ機能が複数のテストに重複している場合、テストの整理と管理が重要。
積み上がる大きな山
エンドツーエンドテストの長時間実行は、生産性を低下させ、デプロイ可能なサービスを減らす。壊れたテストへのチェックインを許可すると、問題が長引き、品質フィードバックが遅れる。テストを高速化し、小さな変更を頻繁にリリースすることが理想。
メタバージョン
エンドツーエンドテストで複数のサービスのバージョンをまとめて管理することは、マイクロサービスの利点である「独立してデプロイ」を放棄することになる。サービスが絡み合うと、最終的には複数サービスのデプロイをオーケストレーションしなければならなくなり、混乱を招き、モノリシックな状況より悪化する可能性がある。
独立テスト容易性の欠如
独立したデプロイ可能性を維持するためには、独立したテストも必要。エンドツーエンドテストはチームの自律性を低下させ、調整が増える可能性がある。複数チームが共有するテスト環境では問題が発生しやすく、理想的には各チームが独自のテスト環境を持つべき。調査によると、高パフォーマンスなチームは統合テスト環境を必要とせず、ほとんどのテストをオンデマンドで実行していることがわかっている。
エンドツーエンドテストを避けるべきか
エンドツーエンドテストは、マイクロサービスの数が少ない場合には有効ですが、サービス数が増えるとテストスイートが肥大化し、管理が困難になる。複数チームでテストスイートを共有すると、独立したデプロイが難しくなる。エンドツーエンドテストは、意味的破壊(後方互換性の問題)の検出に有効ですが、多大なコストがかかる。理想的には、意味的破壊を小さな範囲で検出できるテストを用意することが望ましい。これを実現するために、契約テストやコンシューマ駆動契約が有効。
契約テストとコンシューマ駆動契約(CDC)
契約テスト(Contract Testing)およびコンシューマ駆動契約(CDC: Consumer-Driven Contracts)は、マイクロサービス間の依存関係をテストする手法。契約テストでは、あるサービスが外部サービスに対して期待する振る舞いを記述し、その期待を確認するテストを行う。これにより、実際の外部サービスを使用せずに、スタブやモックを使ってテストを実施できる。
CDCでは、コンシューマ(上流)のサービスがプロデューサ(下流)のサービスにどのような振る舞いを期待するかを契約として明示的に定義し、コンシューマとプロデューサの間で共有される。これにより、プロデューサ側は自分のサービスが期待に応えることができるかをテストする。契約が破壊的変更を含む場合は、エンドツーエンドテストの代わりに、契約テストで問題を早期に発見できる。
具体的なツールとしては、Pactがあり、これはコンシューマ駆動テストをサポートするオープンソースツール。Pactは複数の言語とプラットフォームをサポートし、契約仕様をJSONで定義する。また、契約の管理にはPact Brokerなどを使用できる。
CDCの利点は、マイクロサービスの進化に関する議論をテストとして記録し、サービス間での信頼関係を築くことができる点。特にサードパーティサービスを利用する場合などには、頻繁なコミュニケーションが求められ、信頼関係が重要となる。
結論
エンドツーエンドテストは欠点が多く、特に大規模なマイクロサービスでは必要性が低くなることがある。他の手法(CDCやプログレッシブデリバリなど)が支持され、エンドツーエンドテストへの依存度は減少する。ただし、完全に捨てる前にその必要性を慎重に考えるべき
開発者体験
マイクロサービスが増えると、開発者体験が損なわれる可能性がある。特に、複数のマイクロサービスをローカルで実行する必要がある場合、開発環境が遅くなることがある。この問題への対処法として、クラウド環境で開発・テストを行う方法がありますが、コストや複雑さが増すため、必ずしも最適ではない。理想的には、開発者は自分の担当するマイクロサービスだけをローカルで実行し、他のサービスはスタブ化して対応するのが良いとされている。
本番前環境でのテストから本番環境でのテストへ
以前は本番前環境でのテストが重要視されていましたが、実際の運用環境で問題が発生することがある。分散システムの複雑さから、本番前にすべての問題を捕捉することは不可能。
理想は、ソフトウェアをリリース前に多くのテストを行い、問題を早期に発見することだが、本番環境でもテストを実施することで、より高品質なフィードバックが得られることがある。
本番環境でのテストの種類
本番環境でのテストには、簡単なものから複雑なものまで様々な種類がある。
例えば、マイクロサービスが稼働しているかを確認するpingチェックや、デプロイ作業の一環として行うスモークテストなどがある。また、カナリアリリースを通じて新バージョンを少数のユーザーにリリースし、動作を確認する方法もある。さらに、偽のユーザー行動をシステムに投入してテストすることもあるが、これには本番システムへの影響を避けるため、安全に行う必要があります。
本番環境でのテストを安全にする
本番環境でのテストを安全に実施するためには、システムの不安定化や本番データの汚染を避けることが重要。単純なpingチェックやスモークテストは通常安全だが、偽のユーザー行動をシステムに投入するテストでは慎重な配慮が必要。特に、注文や支払いが発生しないように注意し、リリース前のソフトウェアに対するテストは安全に行うべき。
平均故障間隔( MTBF) よりも 平均修復時間( MTTR) か
本番環境でのテストにおいて、障害発生時の管理を重視するアプローチが推奨される。具体的には、ブルーグリーンデプロイやカナリアリリースを活用し、問題発生時に迅速な修復ができる体制を整える。MTBF(平均故障間隔)とMTTR(平均修復時間)のトレードオフを考慮し、問題を早期に検出し、ロールバックを速やかに行うことが重要。機能テストに多くの時間を費やすよりも、障害復旧の改善に注力することが有益な場合がある。また、ソフトウェアの実際の使用状況を調べる場合には、堅牢なテストを省略することが有意義なこともある。
[3]
機能横断テスト機能横断的要件(CFR)は、システムのパフォーマンス、可用性、セキュリティなどの非機能的な要件で、システム全体に関わる。これらの要件は本番環境で確認されることが多く、早期に検討し定期的に見直すことが重要。また、CFRはマイクロサービス設計におけるトレードオフを生み、テスト戦略を作成する際に考慮するべき。
パフォーマンステスト
パフォーマンステストは、システムの遅延を特定し、改善点を見つけるために重要。マイクロサービス化により、遅延の原因となる呼び出しが増えるため、定期的にテストを実施し、結果を確認することが必要。自動テストを導入し、パフォーマンス目標を設定することも効果的。
堅牢性テスト
マイクロサービスアーキテクチャは、最も信頼性が低い部分と同等の信頼性しか持たないため、堅牢性を向上させるメカニズムが必要。
例えば、複数インスタンスの実行やサーキットブレーカーを使って、障害を許容する仕組みを取り入れる。これらの障害シナリオを再現し、システム全体が正常に動作し続けるかを確認するテストが重要だが、実施にはネットワークタイムアウトの作成などが必要で、少し難易度が高い。
まとめ
テストの全体的なアプローチについて、要点は以下の通り
- 高速なフィードバックを得るためにテストを最適化し、テストの種類を分ける。
- エンドツーエンドテストを減らし、代わりにコンシューマ駆動契約(CDC)を使用する。
- テストと本番環境での問題検出(MTTR)の最適化とのトレードオフを理解する。
- 本番環境でのテストについても検討を開始。
Discussion