🌊

どうして統合テストは重要なんだろう?

2022/09/16に公開

はじめに

最近では、多様なテスト手法や開発者向けツールを散見します。
エンドツーエンド(E2E)テストだけでも、「Cypress」「Puppeteer」「Playwright」「Selenium」などのツールがあります。単体テストでは「Vitest」や「Visual Studio」のビルトイン単体テスト機能など、テストの準備を容易に自動化できます。

ですが、多様なテストツールを導入しても、「When」、「How」を押さえてなければ、テストの効果を有効に得ることができないと考えています。
まず、静的テスト、単体テスト、統合テスト、E2Eテストを実装コスト、実行時間と信頼性の観点で見ていき、無駄や漏れのないテスト戦略を立てていきましょう 。

テストを実装コスト、実行時間と信頼性で考える

どのテストを使用するかの選択する観点として、テストをする実装コスト、実行時間と、テスト結果の信頼性のトレードオフが存在します。
4種類のテスト、それぞれ押さえていきましょう。

静的テスト/ Static Test

  • 実装コスト、実行時間: 低い
  • 信頼性: 低い

静的テストは、基礎的なlinting(プログラミング言語の文法の間違いやエラーを指摘する)や静的解析であり、誤字脱字などの基礎的な問題を見つけるのに役立ちます。
静的テストは最も手軽に、かつ素早く実行できるテストです。ですが、このテストの結果で保証されるアプリケーション全体の品質に関しては高い効果は得られないと考えます。

単体テスト/ Unit Test

  • 実装コスト、実行時間: 少し低い
  • 信頼性: 少し低い

単体テストは、ライブラリのテストや便利関数に対しては最適だと考えています。
それはより小さなロジックやデータ処理に焦点を当てるので、条件付きロジックや関数呼び出しのように、特定の入力セットと出力セットが対応していることを確認するテストになるからです。

統合テスト

  • 実装コスト、実行時間: 少し高い
  • 信頼性: 高い

統合テストでは、バックエンドサービス間やフロントエンドフレームワークのコンポーネント間など、サービス/コンポーネント間の相互作用をテストします。
このテストは、コンポーネントが連携して正しく動作することを確認するために必要になります。単体テストでは、各コンポーネントが正しく動作することが分かるかもしれないが、データエラーやロジックエラーは、コンポーネントを分離してテストしたときにのみ現れるかもしれません。
統合テストでは、テストの実行プロセスをより軽量化できるので、より低コストで実施できます。

E2Eテスト

  • 実装コスト、実行時間: 非常に高い
  • 信頼性: 非常に高い

E2Eテストでは、ユーザーがアプリケーションでアクションを実行しようとするときに遭遇し得る品質問題を想定し、アプリケーションのユーザ体験全体をテストします。一般的に、最も実行時間と実装コストがかかりますが、「ユーザーはアプリケーションに不備を感じないだろう」という確信が得られます。E2Eテストはより高性能なサーバを必要とし、完了までに時間がかかるので、コストもかかります。E2Eテストに非常に長い時間がかかる一因は、バックエンドサーバに対して呼び出しすることにあります。これによって多くのことが分かりますが、厄介な事態に陥ることもあるのです。それは、E2Eテストが失敗した場合、フロントエンドコードのせいではなく、バックエンドに問題がある可能性があるからです。

なぜ、統合テストは重要なのだろうか

Remix.runのKent C.Dodds氏や、VercelのGuillermo Rauch氏などの著名なソフトウェアエンジニアは、統合テストがE2Eテストよりも低コストで、単体テストよりもカバー範囲が広いことを理由に、もっと統合テストを重視するように提唱しています。

Martin Fowler氏の「Testing Pyramid」に手を加えて

Dodds氏は「Testing Trophy」を進言しています。

Fowler氏のテストピラミッドは実装コストが上がるとテストの速度が落ちる傾向があることを示しています。
そのために多くのWeb開発者はE2Eテストを最小限に抑え、単体テストを最大限使用します。

では、テストトロフィーはどうでしょうか?
図を見ただけでは少し分かりにくいので、Kent C.Dodd氏の言葉を引用します。(意訳)
https://twitter.com/kentcdodds/status/960723176031272962
「静的テスト、単体テスト、統合テスト、E2Eテストのバランスはチームが何を重視するかによって変化します。このバランスは、厳密なルールとして捉えるべきものではないでしょう。
あくまで一般論です。使用できるツールでアプリをテストするのがどれだけ簡単か、難しいかによって、実際に異なるのです。 一般的な考え方は、静的テストと統合テストによって、かけたコストに対してユーザに“素晴らしい”価値を保証します。そして単体テストとE2Eテストによって、“良い”価値が得ることができのです。なので全て利用していきましょう」

統合テストの大きな価値の一つは、E2Eテストと同じようにユーザーのふるまいをテストできることにあります。
ユーザーがコンポーネントを単体で使用することはほとんどなく、ほとんどのWebアプリは、異なるコンポーネント間の相互作用として実行されます。
このため、統合テストは、特定のロジックの入力と出力をテストするだけの単体テストと比べて、テスト結果の信頼性が高くなると思います。

なので、最初に書くべきなのは統合テストだと捉えています。 この考え方は、ほとんどの人は直感に反したり、異なる意見を持っているかもしれません。
開発サイクルの中で不具合を発見するのが遅ければ遅いほど、その修正にかかる費用は高くなると教えられてきました。統合テストなどの「大きなタスク」に着手する前に、すべての細部を完璧にしようとします。
先に単体テストを書いていては必要なビジネスロジックを柔軟に変更しながら開発を進めることができません。

ですが、統合テストをむやみに書けばいいとは限りません。良いテストの条件を満たすために戦略を練らなければならないからです。

良いテストを書くには

実行しやすさがある

単体テストと統合テストは分別する必要があります。
混同すると、テストスイートの実行にかかる時間に悲惨な結果をもたらすことがあります。
単体テストは一般的に非常に高速なので、CI環境 でビルドが開始されるたびに実行されます。
コードの基本的な正確さを対象とするため、ビジネスロジックのバグを早期に検出し、バグを発生させた開発者がすぐに修正できるように、頻繁に実行することが重要です。
一方、統合テストは実行に時間がかかるため、すべてのビルドサイクルに含めるのではなく、デイリービルドに近い手法で実行されるべきです。

Logging を入れよう

統合テストの範囲は、どの機能フローにおいても、異なるデバイスや複数のソフトウェアモジュールにまたがる可能性があります。
その結果、統合テストが失敗した場合、その原因を特定するのはより難易度が上がることになる可能性があります。

失敗を分析する唯一の方法は、徹底的なloggingであり、問題の所在を明らかにすることができます。
ですが、徹底的なloggingはパフォーマンスに大きな影響を与えるので、必要なときだけ行うように留意しなければなりません。
運用時には最小限のログしか取らず、問題が発生したときには徐々に詳細なログを取るようにフラグで制御できるloggingを導入するのも良いでしょう。、

まず統合テストから書こう

Walker Royce氏は、この考えを論文 Measuring Agility and Architectural Integrity で詳細に論じています。
彼は、アーキテクチャを正しく理解しないなら、たとえすべての単体テストがパスしたとしても、アプリケーションの実装がすでに書かれている段階で手直しすることは、非常にコストがかかるだろうと明言しています。
彼の言葉を借りれば、「アーキテクチャ(コンポーネントの構成、関係、動作)の正確さをクリアするまでは、コンポーネントの正確さに取り組んではいけない」のです。

さいごに

包括的にテストを書くと4種類のテストを組み合わせて使用します。
ですが、初期段階であれば統合テストの費用対価値は非常に高いと考えています。
間違った知識や内容がありましたら、ご指摘ください。

GitHubで編集を提案

Discussion