🧪

フロントエンドのテスト戦略ってどうすればいいの?

2024/10/07に公開1

こんにちは!
株式会社ココナラの法律相談事業部でWebエンジニアをしている 原井夏樹 です。
ココナラ法律相談というプロダクトのフロントエンド・バックエンド開発を担当しています。
よければXのフォローをお願いします!喜びます! @superhahnah

この記事ではフロントエンドテストの導入にあたり、私たちのプロダクトではどんなテスト戦略をとるべきかを調査・検討して得られた知見を共有します。
読んでくださる皆さんの参考になれば幸いです。

そもそもフロントエンドのテストって色々ありすぎてよくわからん

「〇〇テスト」っていう用語、本当にたくさんありますよね。 例えば…

  • ユニットテスト(単体テスト)
  • インテグレーションテスト(結合テスト)
  • E2Eテスト(End to End テスト)
  • 手動テスト
  • ロジックテスト
  • インタラクションテスト
  • スナップショットテスト
  • ビジュアルリグレッションテスト
  • アクセシビリティテスト
  • パフォーマンステスト
  • クロスブラウザテスト

などなどです。他にもたくさんあると思います。

私には疑問がありました。
これらのテストって重複のない概念になっているんでしょうか?
全部やればいいんでしょうか?
どれからやればいいんでしょうか?

ナニモワカラナイ😇

自分なりにテストの種類を整理してみた

結論から言うと、上記のテストには テストの粒度テストの観点 についてテスト分類が、ごちゃ混ぜになっています。
※あくまでも私なりに考え解釈したものです

テストの粒度に着目したテスト分類

ユニットテスト、インテグレーションテスト、E2Eテストはテストの粒度に着目したテスト分類です。 「テストの粒度」という表現は「テストの対象」と言い換えても良いでしょう。

ちなみに「手動テスト」はテストの方法ですね。対比としては「自動テスト」となります。

以下に、テスト粒度に関するテストの分類をまとめてみます。
ユニットテストとインテグレーションテストの説明についてはhttps://kentcdodds.com/blog/the-testing-trophy-and-testing-classificationsを参考にしました。

テスト分類 説明 フロントエンドにおける例
ユニットテスト 依存関係 (コラボレーター) がまったくないユニット、またはテスト用にモック化された依存関係を持つユニットをテストするテスト分類。 独立したUIコンポーネント、独立したカスタムHook(Reactの場合)や関数のテスト。依存するカスタムHookの挙動がモックされたUIコンポーネントのテスト。など。
インテグレーションテスト 複数のユニットが互いに統合されているかどうかをテストするテスト分類。 別のUIコンポーネントやカスタムHookに依存したUIコンポーネントのテストなど。
E2Eテスト フロントエンドからバックエンド・インフラまで、ユーザーが実際に操作するのと同等の環境下で、ユーザー操作に対する挙動を自動的にテストするテスト分類。 ブラウザからのログイン操作フローのテストなど。

テスト分類の定義は1つに定まらないことには注意してください。
前述の参考記事の中では、1990年代においてでさえ、「ユニットテスト」という単語には24種類もの定義があったと言及されています。

テストの観点に着目したテスト分類

ユニットテストやインテグレーションテストは前述のようにテスト粒度に着目したテスト分類ですが、以下に挙げるようなものはテストの観点に着目したテスト分類だと考えられます。

  • ロジックテスト
  • インタラクションテスト
  • スナップショットテスト
  • ビジュアルリグレッションテスト
  • アクセシビリティテスト
  • パフォーマンステスト
  • クロスブラウザテスト

これらのテストは基本的にはどのテスト粒度でも行うことが可能です。
例えば、「ユニットテストの粒度でビジュアルリグレッションテストをする」こともできれば、「E2Eテストの粒度でビジュアルリグレッションテストをする」こともできます。

この例をもう少し具体的に説明しましょう。
まず「ユニットテストの粒度でビジュアルリグレッションテストをする」方法です。これには、ユニットなUIコンポーネントをUIエクスプローラー(StoryBookなど)上で表示し、それをキャプチャし、以前のものと比較して異変がないかをテストします。
次に「E2Eテストの粒度でビジュアルリグレッションテストする」では、E2E環境でとあるページの全体をキャプチャし、以前のものと比較するテストを行います。

これらはいずれもビジュアルリグレッションテストをしていますが、テストする粒度が大きく異なります。インテグレーションテストの粒度でも行えます。

ただ、全部の「テスト粒度」と「テスト観点」の組み合わせを網羅すれば良いかと言うと、現実的にはそうはいきません。かけられる時間もお金も有限ですし、ケースによっては効果が薄いものもあると思います。
自分たちにとって重要なものやコストパフォーマンスに優れるものを優先し取捨選択することになるでしょう。

フロントエンドのテスト戦略にはどんなものがあるか

この章では世の中で一般的に言われているテスト戦略について取り上げます。
その前に、まずは各テスト粒度の特徴をおさえておきましょう。

テストのリアリティ 実行の安定性 実行速度 作成コストの低さ メンテナンスコストの低さ
E2Eテスト ★★★ ★☆☆ ★☆☆ ★☆☆ ★☆☆
インテグレーションテスト ★★☆ ★★☆ ★★☆ ★★☆ ★★☆
ユニットテスト ★☆☆ ★★★ ★★★ ★★★ ★★★

E2Eテストはテストとしてのリアリティが最も高いです。制約がないのであればE2Eテストを優先して充実させることが理想ですが、デメリットが大きく実際そうはいきません。
一方でユニットテストはテストのリアリティと言う面では不十分ですが、その他の観点では優れていることがわかります。
リソース配分に対するリターンを考慮して、どこに比重を置くかの戦略を立てる必要があります。

これを踏まえて、一般的にどんなテスト戦略があるのかを見ていきましょう。
ここで取り上げるテスト戦略は以下の3つです。

  • アイスクリームコーン
  • テストピラミッド
  • テスティングトロフィー

アイスクリームコーン

アイスクリームコーン型のテスト戦略では、手動テストやE2Eテストを充実させ、インテグレーションテストやユニットテストの数・それにかかるコストを抑える戦略を取ります。

アイスクリームコーンの戦略
(画像は https://gihyo.jp/dev/serial/01/savanna-letter/0005 より)

これはテストのリアリティに重きを置いた戦略ですが、現代の技術ではE2Eテストは不安定で実行が遅く、メンテナンスコストが高いです。
この戦略での自動テストでは、品質を保証する役割をこなすことは現実的にはできず、開発生産性が著しく低下してプロダクトの競争力を失うことに繋がってしまいます。

将来的に技術革新が起こりE2Eテストの安定性・実行時間・コストが改善されれば、この戦略が妥当になる可能性はありますが、少なくとも現代ではアンチパターンです。

テストピラミッド

テストピラミッドではユニットテストを最も充実させ、次にインテグレーションテストを重視します。E2Eテストにはそれほど労力を割きません。

テストピラミッドの戦略
(画像は https://gihyo.jp/dev/serial/01/savanna-letter/0005 より)

テストピラミッドは安定的で、実行が高速で、維持しやすい、費用対効果の高いテスト戦略です。
E2Eテストは実行が失敗しやすく、壊れやすくメンテナンスコストが高い上、構築や実行に時間がかかります。人的リソース含めお金もかかります。
テストピラミッドの戦略では、その点は現実の制約を受け入れ、E2Eは必要最小限に留め、他のテストを比較的充実させることを優先します。
この戦略は現実的でコストパフォーマンスにも優れ、信頼性と開発速度のバランスが取れると言われています。

テスティングトロフィー

フロントエンドに限ったテスト戦略で言うと、テスティングトロフィーというものがあります。
詳しくは提唱者の Kent C. Dodds 氏が次の記事で紹介しています。
https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications

テスティングトロフィーの戦略
(画像は上記記事より)

これまで見てきた他のテスト戦略と大きく異なる点は2点です。

  • 基底部分に Static(静的テスト) がある
  • インテグレーションテストの層が最も厚い

順に見ていきましょう。

まず、静的解析が基底部分に、かなりの大きさで示されています。
これは、フロントエンドの領域では静的テスト(コンパイラ、リンターなどによる事前のエラー検出)は当たり前のものではないため、敢えて示されています。
そしてこれは、品質向上の観点で非常にコストパフォーマンスに優れているためかなりの大きさを占めていますね。
TypeScriptなどが適切に導入されていて静的型の威力が発揮できていれば、それだけでユニットテストやインテグレーションテストで検査する内容をかなり減らすことができ、効果は絶大です。

次に、インテグレーションテストの層が最も厚い点に関してです。
これには根底として、「ユーザー操作を起点としたテストに比重を置くべき」という考えがあります。
極論を言うならば、E2Eテストを充実させることがその実現に繋がります。
それはあくまでも極論で、別に押さえておきたいことがあります。
ユニットのUIコンポーネントは実際のWebアプリケーションにはあまり存在せず、ほとんどのものは複数のユニットが組み合わせって動作するUIになっている ということです。

この前提に立って考えるとどうなるでしょうか?
「ユーザー操作を起点としたテストに比重を置くべき」という話と、前述のテスト粒度ごとのコストパフォーマンスを踏まえると、 「インテグレーションテストを最も充実させるべき」 という考えに至ります。

テスティングトロフィーの戦略を選んだ

テスト戦略を選ぶにあたって、アイスクリームコーンはアンチパターンであり候補から即座に外れます。
テストピラミッドとテスティングトロフィーはどちらも良い戦略だと思います。

ここまでの調査を経て「ユーザー操作を起点としたテストに比重を置くべき」という考えに共感したので、私たちの第一候補はテスティングトロフィーとなりました。
ここで、私たちのプロダクトのコードベースにとってそれが適当なのかを確認します。

私たちのプロダクトのフロントエンド部分には以下の実態があります。

  • TypeScriptで書かれており、ある程度の静的テストが導入済みです。これにより、ユニットテストなどにおいて値の型検査などは不要なケースが多くあると思われます。
  • ユニットのUIコンポーネントよりも、インテグレーション層のUIコンポーネントの方が、UIの数もソースコードのボリュームも多いです。
    • この事実は、ユニットのUIかインテグレーションされたUIかによらず、テストを充実させていけば自然とテスティングトロフィーが達成されるということでもあります。

これらの実態も踏まえ、私たちのケースではテスティングトロフィーの戦略に乗ることは望ましく、かつ実現可能性が高いと考えました。

テスト導入の優先順位を考える

テスティングトロフィーの戦略をとることが決まり、インテグレーションテストを充実させることが目標になります。
しかし一切テストがない状態からいきなりインテグレーションテストを作成するのは初期実装コストが高そうに思われたので、まずはユニットテストのテスト実装から始めることにしました。

ところで、テスト粒度に関してはテスティングトロフィーの戦略をとると決まりましたが、テスト観点についてはどうしたらいいでしょうか?
以下にテスト観点を再掲します。

  • ロジックテスト
  • インタラクションテスト
  • スナップショットテスト
  • ビジュアルリグレッションテスト
  • アクセシビリティテスト
  • パフォーマンステスト
  • クロスブラウザテスト

これらの中でコストパフォーマンスに優れ、優先度が高いと考えたのは、ロジックテストとインタラクションテストです。
インタラクションテストは操作時の挙動をテストするので実際のユーザー操作に近いという観点で重要なテストと言えます。
一方、ロジックテストは実装が簡単かつ(私たちのプロダクトでは)ボリュームも少ないので、はじめの一歩として取り組むにはこれが良いと思いました。
また、私たちのチームではフロントエンドのテスト導入・実装自体が初めてだったというのもあり、難易度の低いものから取り組むのが良いだろうという視点もありました。

結論として、テスト粒度とテスト観点の両方を踏まえて、次のような順番でテストを充実させていく戦略にしました。

  1. ユニットのロジックテスト
  2. ユニットのインタラクションテスト
  3. インテグレーションのロジックテスト
  4. インテグレーションのインタラクションテスト
  5. それ以降は改めて優先度判断

現状、私たちは 1.ユニットのロジックテスト については実装を既に完了したところです。

戦略はあくまでも基本的方針であり柔軟性も必要

テスティングトロフィーに則ってインテグレーションテストを充実させると言っているのにも関わらず、上記に示した実装順ではユニットテストから着手するようになっています。
これは、私たちのプロダクトにおいてユニットのUIや関数、カスタムHookがそこまで多くないため簡単に実行できる、という背景があります。
前述の通り、初めてのフロントエンドテスト導入・実装ということもあるので難易度も鑑みています。

また、完全に上記の順番通りにやるかといえばそうでもなく、全部のユニットテストを実装するより先に、特定のインテグレーション層の重要なUIを優先的にテスト実装するなどはすると思います。

更に少し極端なことを言うと、特に重要で品質担保が求められる操作フローなどがあるのであれば、その部分のE2Eテストだけを優先的に実装するようなこともあり得るでしょう。

基本的には決めた戦略に則って進めることになります。しかしそれだけではなく、プロダクトにとっての重要度を考えてテスト実装の優先順を柔軟に決定していきたいと考えています。

さいごに

この記事ではフロントエンドテストの導入にあたり、どんなテスト戦略をとるべきかを調査・検討した内容を共有しました。

本記事をご覧になって興味を持たれた方、もっと詳しい話を聞いてみたい方はぜひ下記のカジュアル面談応募フォームよりご応募ください!
https://open.talentio.com/r/1/c/coconala/pages/70417

また、募集求人についてはこちらからご確認ください。
https://coconala.co.jp/recruit/engineer

あ、 私のXのフォローもお願いしますね! @superhahnah

Discussion

YABUKI YukiharuYABUKI Yukiharu

テストの種類に、プロパティベースドテスト(Property Based Test:PBT)も追加してもらえるといいかなと思いました。