フロントエンドのテスト構成について考えてみた in 2023
はじめに
この記事では、
- フロントエンドの開発において意義のあるテストはなにか?
- それらをコスパよく実現するためにはどうすればよいか?
について考えて、作った構成を紹介します。
前提
下記の技術スタックを利用していますが、これ以外のスタックでも応用可能な仕組みが多いと思います。
- Next.js
- Storybook
- playwright
- msw
- msw-snapshot (拙作)
注意事項
この記事の構成は、まだまだ実験的な機能だったり怪しい技術が一部採用されています。
- msw-snapshot
- 拙作のライブラリであって、動作が怪しい可能性がめっちゃあります。
- Next.js の testmode
- playwright + msw を実現するために必要でした。
- まだまだ全然まともに動かないかもしれません。(サンプルリポジトリの単純なテストは動いた)
サンプル
下記のリポジトリにサンプルを用意しています。
このリポジトリの構成を利用すると、下記のようなテストが利用可能になります。
Unit / Smoke Test
目的
例えば、30 個のコンポーネントが存在する状態で radix-ui のバージョンを上げる
みたいなことは、そこそこ発生する作業な気がします。
このようなケースでは、すべてのコンポーネントに対してランタイムエラーが発生していないか?を簡易的にでも確認できると安心できそうです。
方針
@storybook/test-runner
を利用して、すべての UI コンポーネントを Smoke Test します。
複雑なコンポーネントは @storybook/addon-interaction
を使って Unit Test をします。
サンプルの package.json#scripts.test:smoke
あたりに実装してあります。
この仕組みは UI コンポーネントを Storybook で開発している状態さえ作れれば勝手に維持されます。
非常に高コスパで便利です。
Visual Regression Test
目的
例えば 30 個のコンポーネントが存在する状態で tailwind.config.js の設定を変更する
といった場合、実際にどの UI コンポーネントに変化があるか?を人力で把握するのが困難です。
このような場合には、機械的に全 UI の表示差分を確認できる仕組みが必要になります。
方針
main
ブランチが更新されたら、main
ブランチのビジュアルスナップショットを作成して GitHub Actions
の artifact に保存します。
あとは Pull Request 毎の CI で artifact をダウンロードして差分確認をするだけです。
こちらの仕組みも UI コンポーネントを Storybook で開発している状態さえ作れれば勝手に維持されます。
この仕組みは CI 周りが少しだけトリッキーですが、サンプルはかなり単純化してあるので見ればわかると思います。
Integration Test
目的
フロントエンドがバグってしまう原因は様々考えられます。
- なんらかのパッケージを更新したところ、挙動が変わっていてバグる
- UI コンポーネントをリファクタしたら、意図しないページがバグる
- 普通にポカミスでバグる
こういったことを防ぐためにはリグレッションテストが欲しくなりますが、我々が開発しているのはウェブサイトであるため、可能であれば実際のブラウザを利用したシナリオベースのテストを書いていきたいところです。
方針
実際のブラウザを利用したシナリオベースのテストは、フロントエンドエンジニアの悲願だと思われますが、様々な課題があります。
- API を叩くテストは不安定になりがちなのでモックをしないといけない
- API のモックを用意するのはかなり面倒です。
- モックは古くなります。メンテが必要でかなり面倒です。
- 外部システムに依存するテストになりがちで、CI による自動化が困難になりがち
- バックエンドシステムが新 API をリリースしない限り CI できないといったことが考えられます。
これらを軽視して仕組みを導入しても「書くのがだるく、自動化されていない」のでテストが腐っていきます。
これを解決するために、playwright + msw + msw-snapshot
を用いて下記のようなワークフローを構築してみました。(まだ「やってみました」という段階で、実運用に耐えるかどうかはこれから検証します。)
- ローカル開発時は
msw-snapshot
を無効化する - ローカル開発時に E2E テストを書きながら開発する
- 開発が完了したら
msw-snapshot
が生成した API スナップショットも一緒にコミットする - CI 環境上では
msw-snapshot
を有効化することで全 API がモック込みでテストされる
msw-snapshot
は拙作のライブラリですが、今のところそれっぽく動いています。
スナップショットの生成条件などはまだまだ調整が必要そうなので、これから頑張っていきます。
E2E Test
目的
例えば、バックエンドチームが API のリファクタリングをリリースしたところ、フロントエンドチーム管轄のウェブサイトが壊れてしまった!ということは普通にありえるケースかと思います。
このようなリグレッションは Integration Test を API モックなしで継続的に実行することができれば自動的に検出することができそうです。
方針
下記のようなタイミングで msw-snapshot
を無効化した Integration Test を走らせることができれば目的は達成できそうな気がします。
(実際は flaky なテストが増えそうなので、やってみたら全然ワークしないかも...)
-
main
ブランチが更新されたタイミング - バックエンドチームがリリースしたタイミング
- ステージング環境を対象にすればリリース前の検知も現実的かもしれません
おわりに
現段階でのフロントエンドのテスト構成 in 2023 を紹介してみました。
正直、Integration Test や E2E Test はこれから実験していくいった段階であり、まだ上手くワークするかどうかすらわかっていませんし、拙作の msw-snapshot
にも色々バグがあるかもしれません。
テスト戦略を考える上では「テストにはコストがかかる」ということを忘れず、バランスを取りながら意思決定することが大切だと思います。
闇雲に「テストは必須!!!」とするのではなく、目的を考えた上で「安くて美味いテスト」を求めて研究を続けていきます。
Discussion
正直、
Integration Test
とE2E Test
って何が違うの?って思っている人間だったりしますが、この記事はIntegration Test = モックを活用して決定性を高めた E2E Test
という解釈で書いています。お前の解釈はおかしい!みたいな指摘はめっちゃ歓迎なのでぜひコメントください!