💡

マイクロサービスにおけるテストの考え方

2024/03/17に公開

最近マイクロサービスのテストに関して考えることがあり、少なくとも最悪でないテストの指針を考えたのでメモとして残します
機能テストの話になります

複雑すぎるマイクロサービスのテスト

自分はそれなりに大きな会社で開発をさせてもらっているので、1サービスが複数の部署やチームで運営されているような環境にあります
ECサイトでいったら、クーポンコンポーネント、ポイントコンポーネント、カートコンポーネントのように、自分が運用しているサービスでは文脈に沿っていくつかの機能が複数のコンポーネントに分割されたマイクロサービスで構築されています
当然コンウェイの法則に従って、チームや部署も複数存在しています

このようなサービスで開発をしていると複数コンポーネントに跨る改修が入ったときのテストがとても複雑になります
例えば、ECサイトを運営しており、新しいポイントの仕組みを追加した場合を考えてみます
ポイント自体を扱うコンポーネントはもちろん、料金を計算するコンポーネントにも影響がありそうです
ここでは料金を計算するのはカートコンポーネントの役割だとします
このときテストは誰が何をするのが正解なのでしょうか?
カートコンポーネントのチームはポイントが想定どおりに計算されているのを確認すべきでしょうか?
それともポイント自体の計算はポイントチームで担保されているので、インターフェースが間違ってないよね程度の疎通で問題ないのでしょうか?
このあたりの認識合わせができておらず、テストが不十分なままリリースされシステムダウンにつながる、過剰なテストをしてしまい余計なコストをかけてしまったというのは少なくないと思います

こういった事態を避けるために、マイクロサービスのテストの指針を用意する必要があります

テストの定義

そもそも皆さんはどういうテストをどういう名前で呼んでいますか?
自分の中ではテストというものに明確な定義はなく、100人いれば100種類のテストの定義があると思っています
過去の自分の経験でいうと単体テストという単語だけでも、スコープなどは関係なく自動化されたテスト全般を指しているプロジェクトや、コンポーネント単体で動かせるテストを指しているプロジェクト、関数コンポーネントテストを指しているプロジェクトなど、複数の定義をされているのを経験してきました

この経験からわかるように単語は色々な文脈で色々な定義をされているので、まずテストの定義からします
テストは下記のような2軸で表される表のどこかにプロットできると思っています
よくあるテストサイズとテストスコープのはなしです(この記事など)

もとの記事の方はテストの概念の統一化を図っている方なので具体的なテストサイズを定義していますが、この記事ではそこには深く触れません

ここで重要なのは関係者間でどの位置にどんなテストがプロットされるかの認識があっていることです
ここの認識があっていれば、組織をまたぐテストでも過不足なくテストが実施できるはずです

先ほどのECサイトのポイントの例を考えてみます
関係者の話し合いでこのようなテストを定義したとします

テスト名 つなぐ対象 確認観点
ユニットテスト 関数 ロジック
- ポイントが正しく計算されるか
- 料金は正しく計算されるか
インテグレーションテスト 1コンポーネント エラーハンドリング、整合性
- 例外を吐いたときに正しいhttpステータスを返すか
- 複数のクラスをつなげたときに意図した値を返すか
内部結合テスト チーム内の全コンポーネント 疎通確認
- 各コンポーネントどうし通信を行えるか
- インターフェースが噛み合ってないところはないか
e2eテスト 全コンポーネント シナリオ
- 商品を購入してポイントが付与されるか
- キャンセルしたらポイントが引き剥がされるか

この定義はこのようにプロットできそうです

横軸、縦軸ともにもれなく定義できてそうです

また、図を見てもらえばわかりますが、定義上はデジタルに分けてますが、実際にはグラデーションのように各テストでの確認観点は重なります
例えば一部処理は他のコンポーネントの出力によって切り替わるロジックがあるので、他のコンポーネントに繋がないと安心できないといった意見や、チーム内のコンポーネントで完結するシナリオがあるので、内部結合で実施しますといった事象などがあるからです
デジタルにすべてを定義できるのが理想ですが、そうはいかないケースが多いので、そのあたりは臨機応変に対応する必要があります
大切なのは完璧なテスト定義を作ることではなく、テスト定義の共通認識をもち、過不足なくテストを行うことです

どこを確認するか

無事テストを定義することができましたが、これで終わりではありません
次に、この確認観点をどこで確認するかを決める必要があります

先ほどの例の内部結合テストで考えてみます
ポイントチームのコンポーネントはこのような構成になっているとします

ポイントapiは他のチームに公開するために機能をまとめたapiです
計算apiではいくらポイントを付与するのかの計算、不正判定apiではユーザーにポイントを付与していいのかを判定します

さて内部結合テストにおいて、計算api、不正判定apiのような境界にないコンポーネントに関してテストを実施すべきでしょうか?

自分の考えではテストをする意味はないです
理由は境界にあるコンポーネントでの動作がすべてだからです
詳細まで見れば各コンポーネントは分かれていますが、他のチームからそんなことは興味ありません
基本的にすべてのテストは利用者にとって興味があることを確認することが大切です
関数を利用している人は入力と出力が意図した通りであることに関心があり、他サービスにとっては仕様書どおりのインターフェースであること、ユーザーにとっては商品の購入など特定のシナリオを達成することに関心があります
そう考えると内部結合テストでは、計算apiが意図した通りに動いてても、ポイントapiが動いてなかったら意味がないのです
逆に、計算apiが意図した通りに動いてなくてもポイントapiが意図した通りに動いてれば問題ないのです

例として極端だったかもしれないですが、各テストにおいて利用者が期待していることを意識し、境界にあるコンポーネントでの動作を確認することで、過不足なくテストを実施できます

まとめ

長文で文字文字しくなってしまいましたが、マイクロサービスにおけるテストの書き方に関して色々語ってきました

重要な点としては以下です

  • テストの定義を関係者内で一致させること
  • 境界となるコンポーネントで動作確認すること

この点を意識すると、過不足なくテストが実施でき、より安全に、より低コストに機能がリリースできると思います

Discussion