楽して効果を得るフロントエンドテスティング

2023/02/26に公開

フロントエンドのテスト手法もだいぶ浸透してきましたね!
Testing Trophyなどの教科書的な知識は、すでに多くの人が知るところだと思います。

しかし現場でフロントエンドのテストが広く普及しているかというと、そうではない印象を受けます。
去年フロントエンドの採用面接を担当したときにテストについて質問するようにしていましたが、経験あるエンジニアほど「実際難しいよねぇ」という反応でした。

一方、バックエンド開発では「テストがないなんてあり得ない」という感覚が広く共有されています。方法は広まってるにも関わらずフロントエンドでテストが普及しない理由は何なのでしょうか?

テストがあって嬉しかった経験の有無

フロントエンドは、バックエンドと異なり、「テストがあって嬉しい」という感覚が得られにくいと感じます。
バックエンドでは、しっかりとテストを書くことで、「リファクタリングした際にテストが落ちてバグを未然に防げた」「テストがあるから安心してライブラリのバージョンを上げられる」といった経験が自然と得られます。
つまり、テストの利点を身体で感じ、自然にテストを書くようになるのです。

一方、フロントエンドでは、テストによってバグを防げたり、テストがあるから安心してライブラリのバージョンを上げられるといった経験が得づらいと感じます。さらにデザインは変わりやすいため、テスト直すのが面倒という経験ばかりが積み重なっていきます。

この状態でテストを強制するだけだと、形だけのテストが量産されがちです。
本当の意味でテストを浸透させるには、あって嬉しいと心から思える体験につながる必要があります。

目指すべきものはなにか

私がテストで目指したい状態は、ライブラリやフレームワークのバージョンアップが比較的容易にできる状態です。
バージョンアップのたびに大規模なQAを回す必要があると、最新のバージョンに追いつかなくなってしまいます。
実際、言語やフレームワーク、ライブラリのバージョンが数年前のまま更新されないプロジェクトを多数見てきました。

一方、容易にバージョンアップができると、安心してリファクタリングができ、ライブラリの更新にも気軽に追随できます。この開発体験は非常に素晴らしいものですね!
この状態を実現するために、最も簡単なテストの書き方は何かを考えていきたいと思います。

フロントエンドテストの実情

コンポーネントの単体テスト

よくあるのは、Propsで受け取った値が特定のDOMに表示されているか、APIからの戻り値が適切に描画されているかなどです。
さらにaria属性でDOM指定するようにすればアクセシビリティもテストできて、一石二鳥ですね!

しかし実際問題として、この方法で全要素をテストするのは難しいです。
「ユーザー一覧というタイトルが適切に表示されている」「ヘッダーが表示されている」「招待ボタンが表示されている」「テーブルの各rowにAPIから取得したユーザー情報が表示されている」...
どんなに書いても、どこかで漏れが出てきてしまいます。
特にフロントエンドで起こる不具合は「DOM構造が変わってスタイルがあたらなくなった」「マージンが変わってレイアウト崩れた」といったものが多いですが、こうした見た目のテストには不向きです。

もちろん無意味なものではなく、防げる不具合も多くあるでしょう。
しかし目指したい安心感に辿り着くには、十分ではありません。

スナップショットテスト

実際の出力を網羅的に比較できるので、漏れのなさにおいて理論上最強のテストですね!
出力される文字列の変化も、マージンの変化も、あらゆる変化を検知することができます。

しかし問題なのは、差分が正しいのか判断しづらいことです。
PRでdiffを見てもそれが適切な変更なのか判断し辛く、次第に形骸化していきがちです。
ライブラリをバージョンした際に差分が出ないかなど、もしかしたら嬉しい瞬間もあるかもしれませんが、労力の割には効果が得られづらい印象でした。

楽して効果を得るフロントエンドテスティング

Visual Regression Testing

そこで目をつけたのがVRT(Visual Regression Testing)です。
結局のところAPIの戻り値が正しく表示されるかも、マージンが崩れてないかも、画像の比較さえできれば検知することができます。
つまりVRTを中心にしたテスト戦略を考えれば、「Reactのバージョンをあげてもテスト落ちてないからオッケー」と思えるぐらいの安心感が得られるのではないかと考えました。

Storybook + MSW

多くのプロジェクトでは、すでにStorybookが導入されています。コンポーネントのStoryを作成することが、ルールとして定着しているチームも多いでしょう。そのため、各StoryでVRTを行うことができれば、専用のテストコードを記述することなくテストを行うことができます。

APIとの通信に関しては、MSWでモックすることで、さまざまなケースを網羅することができます。
たとえば、APIの戻り値が空の場合、APIの戻り値が3件の場合(ページネーションなし)、APIの戻り値が100件の場合(ページネーションあり)の3パターンがある場合も、それぞれMSWのパラメータを変えたStoryを用意するだけでテストが可能です。さらに、PRでcommitごとにStoryをビルドすることで、レビュアーが動作確認を行うこともできますね!

私はChromaticを導入しましたが、VRTのテスト基盤自体はこうしたサービスを利用することですぐに整えることができます。

操作に対するテスト

VRTで描画のテストができれば、あとは操作に対するテストのみですね!
たとえば、「ボタンを押したらモーダルが出る」「フォームに不正な文字列を入れたらエラーメッセージが出る」「ページネーションできる」などのテストは、別途書く必要があります。

これはどう書いても良いですが、Storybookのplay関数でテストすれば用意したコンポーネントを使いまわせるし、視覚的にテストすることができて便利です。

おわりに

この記事では、フロントエンドのテストがバックエンドに比べて普及していない理由を探りつつ、VRTを中心としたフロントエンドテスト戦略について記述しました。
Chromaticを使ったVRTの方法やplay関数を使ったテストなど、実際のやり方についてはすでに多数の記事があるのでそちらをご参照ください!

Discussion