フロントエンドのテスト

先日チーム内でフロントエンドのテストについての話になったので軽くまとめておきたいと思います。

テストの単位

フロントエンドのテストは大きく以下の4つに分けられると思います。

静的解析

ESLintによる静的解析を始め、TypeScriptにおける型チェック等も含みます。

個人的にはここは最低限必要なところかと思います。TypeScriptにおいてはそもそもコンパイルできない訳ですし。

ESLintも単純な構文エラーやチーム内でのコーディング規約から外れたものを検知できるので最低限担保すべきコードの質を維持する上で絶対かと思います。

単体テスト

ReactにしてもVueにしてもコンポーネント駆動開発が基本です。

バックエンドだとクラスごとに単体テストを書くようにコンポーネントごとの単体テストが必要になってくると思います。

今回の記事はこの単体テストを中心に書いていきます。

結合テスト

複数のコンポーネントやクラスを組み合わせて正しく動作するかを確認するテストです。

またReactだとHooks等の副作用も対象に含みます。

E2Eテスト

実際にブラウザ上で各シナリオ(テストケース)ごとに期待された挙動になるかどうかを確認するテストです。

テストツールとしてはCypressPlaywrightSelenium等があります。

以上4種類のテストについてはTesting Trophyが有名です。

引用元: https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications

最上位のレイヤーにE2Eテスト、最下層が静的解析となっています。

上のレイヤーに行くほど、

  • フィードバックは遅く
  • 実装コストは高く
  • 信頼性は向上する

という傾向があります。

これは以前エンジニア文化祭の記事で少し触れたテストピラミッドと同様です。

E2Eテストは信頼性が高いものの実行にも時間がかかり、また修正も容易ではありません。

一方で単体テストは修正や実行時間は短くて済みますが、アプリケーション全体のテストではないので信頼性という観点ではE2Eテストに劣ります。

つまりレイヤーごとにメリット・デメリットを比較し、どのレイヤーのテストに力を入れるのがチームやプロダクトにとってコスパが良いのか、戦略を立てる必要があります。

単体テスト

個人的には単体テストを厚くしていくことが基本だと考えています。

理由としては、

  • 実装コストが低いこと
  • 現代のフロントエンドはコンポーネント駆動であること

が挙げられます。

特に2点目はできるだけ小さい粒度でのテストを積み上げていくことがシステム全体の信頼度を上げることに繋がると考えているためです。

先程書いた通りコンポーネントが最小の単位なのでそこの単体テストは必要になるだろうということです。

では具体的にどうテストしていくのかですが、現状の考えとしては以下のとおりです。

  • コンポーネントの見た目やDOM操作等の関数: Storybook
  • DOM操作以外のロジックを伴う関数: Jest / Vitest

基本的にはテストツールはJest (Viteを使っている場合はVitest)で良いと思います。

ただコマンドライン上で動くので例えばwindowを使う関数などはそのままでは使えません(モックする方法はあるみたいですが)。

また、例えばVueのrefを使ってDOMを操作する関数もJest等ではテストしづらいです。

なのでそうしたものについてはStorybookを使ってコンポーネントの見た目と併せてテストするのが良いのではと思います。

逆に言えばそれ以外の関数はすべてJest / Vitestでテストを書く方針で良いと思います。

特にNuxt3ではComposablesによって関数を切り出すことができるようになったのでよりテストしやすくなりました。

DOM操作以外のロジックを伴う関数に関しては/composables直下にドメインごとに切り出してテストを書いていく、という方向で考えています。

まとめ

以上、フロントエンドのテストについてまとめてみました。

まだフロントエンドのテスト戦略についてはベストプラクティスがわからない状態なので試行錯誤しながら進められたらと思います。

その前にNuxt3へのアップデートをしっかり完了させたいと思います。