1. はじめに
2010年代前半に登場したReactやVue.jsに代表される宣言的UI実装は、大規模なSPAの構築を可能にしました。その一方、フロントエンド領域に新たなアーキテクチャが導入されたことで、それまでWebアプリケーション開発で定石とされたテスト手法を適用しづらいケースが増え、新たなベストプラクティスが求められるようになりました。
その要請に応える形で、2010年代後半にはフロントエンドのテスト手法に緩やかなパラダイムシフトがありました。この記事ではそのパラダイムシフトを振り返りながら、フロントエンドで必要なテストについて考察し、最後にChromaticを用いたビジュアルリグレッションテストを紹介します。
2. Testing Pyramid と フロントエンド
テストを語る際によく持ち出されるメタファとして、Testing Pyramidがあります。Testing PyramidはMike Cohnが提唱したテスト戦略に関するガイドライン*2で、テストを3つのレイヤーに分け、それぞれのレイヤーをどの程度厚くするべきか示しています。
各レイヤーの命名は、フロントエンドの世界ではミスリーディングにつながるため、個人的には次のように定義しています。上から順番に、
- UI Testing:E2Eテスト。
- Service Tests:結合テスト。複数のモジュールを扱うテスト。
- Unit Tests:単体テスト。単一のモジュールのテスト。
下層レイヤーほど実行速度が速く堅牢なため、下層レイヤーのテストほど比率を上げるべきという論旨です。
しかし現代のWebアプリケーション開発では、ビジネスロジックはバックエンドに寄せ、フロントエンドは可能な限りプレゼンテーションに集中させることが定石です。WebアプリケーションのアーキテクチャはTesting Pyramidが提唱された10年以上前から変容し、ビジネスロジックの薄いフロントエンドではTesting Pyramidを適用しづらいケースが増えてきました。
3. Testing Trophy という新しいトレンド
フロントエンドにおける新たなテスト戦略のガイドラインとして提唱されたのが、Kent DoddsのTesting Trophyです*3。またKent Dodds氏はTesting Trophyの提唱と共に、そのガイドラインを実践するためのフロントエンド用テストライブラリとして、Testing Libraryを書いています。
図2の上から順番に、
- End to End:E2Eテスト。
- Integration:結合テスト。他のComponentに依存するComponentを指す*4。Atomic Designで言うMolecule以上に相当。
- Unit:単体テスト。他のComponentに依存しないComponentを指す*5。Atomic Designで言うAtomに相当。Reactのhooksもここに位置づけられる。
- Static:Lintや型チェック。
のようにレイヤーが並びます。図中の各レイヤーの大きさは書くべきテストの量を示しており、Integrationテストの厚いレイヤーが特徴です。Testing Trophy(およびKent Dodd氏)が提唱している重要な2つのポイントを順番に見ていきましょう(筆者の個人的意見を含んでいます)。
3-1. ポイント(1) Integrationレイヤーのテストを多く書く
まずTesting Trophyはフロントエンドのためのガイドラインであり、バリデーションやビジネスロジックの安全はバックエンドで担保されているという前提を置けます。そのため、フロントエンドではUnitテストの網羅率を上げるより、ROIの高いIntegrationテストを重視する動機があります。
3-2. ポイント(2) ユーザーの視点へ回帰する
「テストは呼び出し側の視点で書く」という考えは、Testing PyramidやTesting Trophyなどと関係なく、あらゆるテストにおける定石です。たとえばバックエンドのクラスにおけるユーザーとは、そのクラスの呼び出し元です。呼び出し元の関心はクラスの入力と出力であり、クラスの内部実装にまで関心を持つべきではありません。
一方、フロントエンドにおけるComponentのユーザーとは、Webアプリケーションの閲覧者です。閲覧者はクラス名やDOM構造に関心はなく、テキストや色やインタラクションに関心があります。したがって、Componentのテストはクラス名やDOM構造など、閲覧者が関心を持たない領域に関しては不可知であって構わないのです*6。
3-3. Testing Trophyのポジション
Testing TrophyやKent Dodds氏の提唱は議論を呼びましたが、フロントエンドの世界で概ね受け入れられました。その裏付けの一つとして、Create React App(ReactのメンテナであるMeta社が提供するセットアップツール)で生成されるReactアプリケーションには、React Testing Libraryが標準でインストールされます。React Testing Libraryは、上述のKent Dodds氏がTesting Trophyを実践するために用意したTesting Libraryスイートのうち、React用のパッケージです。
また、ThoughtWorks社が技術トレンドを分析し不定期で刊行しているTechnology Radarというガイドラインがあります。2020年5月のTechnology Radarで、React Testing LibraryはReact Hooksと並び、プロジェクトへの導入を推奨する"ADOPT"に分類されました*7。
4. ビジュアルリグレッションテストとは
ビジュアルリグレッションテスト(以後、VRT)とは、テスト実行時にUIのスクリーンショットを撮り、次のテスト実行時に以前のスクリーンショットとの差分をチェックする、回帰テストの一種です。VRTはTesting Trophyのうち、IntegrationテストとUnitテストに属します。
VRTは他の種類のテストと比べ維持コストが低いこと、さらに3-1節と3-2節で述べた2つのポイントと相性が良いため、フロントエンド開発では積極的に導入したいテストです。相性の良さについて詳しく見てみましょう。
4-1. Integrationレイヤーのテストを増やしやすい
モダンなプロジェクトであれば、フロントエンドはComponentをベースとした開発が主流です。またStorybookに代表されるカタログツールを導入しているチームも多いと思います。カタログツールを導入していれば、既に各Componentを単体でレンダリングする環境が整っているため、あとはVRT用にスクリーンショットを撮り比較するだけです。
したがってコンポーネントのスクリーンショットを撮り比較する環境を構築すれば、カタログツールの運用に大きな工数を加えることなくVRTを導入できます。例えば後述するChromaticは、Storybook上のstoryからスクリーンショットを撮り差分を検出してくれるため、Storybookを採用しているチームであれば、導入と運用の工数を抑えられます。
ChromaticのようなSaaSを用いる代わりに、下記の記事のように、自前でスクリーンショットを比較する環境を作ることも可能です。
4-2. ユーザー視点でテストできる
3-2節の通り、Testing TrophyではComponentの内部実装よりも見た目やインタラクションに重点を起きます。VRTはあくまで視覚的な差分のみを検出するため、ユーザー視点を重視するTesting Trophyの思想と親和しています。これにはテストが壊れにくく、さらに修正が容易(=正しいスクリーンショットを選ぶだけ)という大きな利点があります。
一方、VRTと類似したテスト手法として、スクリーンショットの差分ではなくDOMの差分を検出するスナップショットテストがあります。スナップショットテストはVRTとは対象的に、クラス名やDOM構造など、Componentの内部実装にまで踏み込んだテストです。このようにスナップショットテストはユーザーの関心の外をテスト対象にしているため、壊れやすく、さらに修正に手間が掛かるため、導入には慎重な検討が必要なテストです。
5. Chromaticの基本
ChromaticはStorybookの開発元が提供するVRTのSaaSです。Storybookのstoryからスクリーンショットを撮り、差分を検出します。Storybookの導入が前提ですが、その代わりStorybookを運用しているチームでは、VRTの導入と運用の工数を低く抑えられます。
Chromaticはスクリーンショット数により費用が変わりますが、月5000件までであれば無料プランで賄えます。
この記事では、導入方法については他の記事に譲り、Chromaticの基本的な機能について述べます。
図3はGitHubとChromaticを連携した際、PRのチェック欄に表示されるステータスです。それぞれのステータスの内容は次の通りです。
- UI Tests:VRTの実行結果。Detailsをクリックで差分のチェックページへ飛ぶ。
- UI Review:複数人のレビュアーを設定し、ディスカッションを行う機能。
- Storybook Publish:Chromatic上でStorybookが閲覧可能状態になったかどうか。
5-1. UI Tests
UI TestsはVRTの実行結果を表示します。図4は検出された差分のチェックページで、問題がなければ右上のAcceptボタン、そうでなければDenyボタンを押下します。全ての差分についてAcceptすれば、PRのステータスはグリーンに変わり、一つでもDenyされればレッドに変わります。
5-2. UI Review
UI ReviewはUI Testsと似ており、UI Testsと同じくVRTで差分が出た際にPRのステータスがイエローになります。UI Testsと違う点として、レビュアーの設定とディスカッションの作成が可能です。
レビュアーは複数人設定でき、全員がApproveしない限りPRステータスはグリーンに変わりません。また誰でもディスカッションを開始でき、全てのディスカッションが解決されない限りPRステータスはイエローのままです。
5-3. UI Tests と UI Review の使い方
UI Tests と UI Review は互いに類似した機能で、いずれもオフにできます。ただしUI Testsはあくまで差分検知によるデグレ防止のための機能で、一方のUI Reviewはステークホルダーにデザイン面の確認を依頼する機能と捉えられます。
これらを踏まえ、組織の構成やプロジェクトの性質によって、どちらの機能(または両方の機能)を使うかを決定します。例えば個人プロジェクトならUI Testsだけで十分ですし、フロントエンドエンジニアとUI/UXデザイナーの分業がなされているプロジェクトであればUI TestsとUI Reviewの両方を使う価値があるでしょう。
6. ChromaticのTIPSと注意点
最後に、実際にChromaticの運用を始めて気づいたTIPSと注意点について列記します。
6-1. TIPS
6-1-1. スクリーンショットを撮るstoryを選択できる
デフォルトでは全てのstoryがVRT対象ですが、デフォルトで全てのstoryを対象外にしたり、個別のstoryだけを対象にできます。
実際に私の所属チームでも、まずは試験運用のため、Atomic DesignでいうTemplate層のstoryだけを対象とし、無料プランからChromaticの利用をスタートしました。Chromaticの料金はスクリーンショット数によって決まるため、対象のstoryを絞ることで無料プランで賄いました。
Template層だけに絞った一番大きな理由はスクリーンショット数の削減ですが、他の理由として、Template層のテストは結合テストに該当するためです。すなわち、Template層はOrganism以下のComponentを組み合わせて構成するため、Template層の検証はComponent同士の結合の検証と言えます。Template層より上のPage層は、Template層へのデータ注入を担うものなので、Component間の結合をテストするという意義からは少し外れると考えました。
6-1-2. インタラクション後のスクリーンショットを撮れる
3-2節でユーザーの関心事としてComponentのインタラクションを挙げましたが、Chromaticでは部分的にインタラクションのテストが可能です。
storyにplayという名前のメソッドを書くことで、Componentの表示後にplayメソッドの実行完了を待ち、それからスクリーンショットを撮る機能があります。playメソッドを使うことで、フォーカス・ホバー・入力・クリックとインタラクション後のVRTが可能です。
6-2. 注意点
6-2-1. モノレポに弱い
Chromaticでは1つのgitレポジトリに1つのStorybookが期待されます。したがってgitがモノレポ構成を取り複数のStorybookを内包している場合、ひと工夫する必要があります。公式では次の2つの方法を挙げています。
- 1つのStorybookにVRT対象の全storyを収める。
- モノレポのgitが内包するStorybookの数だけgitレポジトリをcloneなどで用意する。
前者の方が筋は良さそうですが、いずれの方法でも導入と運用の手間がかかります。
6-2-2. 大きなリソースに弱い
実際にリクエストが発行されるようなE2Eとは違い、ChromaticのVRTはブラウザ内のレンダリング処理だけのため、False Negativeが比較的少ないテストです。しかし、スナップショットが撮られるタイミングのズレなどを理由に、予期せぬ実行結果になる可能性があります。
例えば大きなリソースを扱ったstoryでは、スクリーンショットを撮るタイミングがズレたり、Chromatic上でのComponentのレンダリングが完了しないなどのケースがありました。そのため所属チームでは、次のような方法で可能な限り安定性を考慮したstoryを書くようにしています。
- 画像などのリソースはStorybook内に静的ファイルとして収め、CDNなどに置かない。
- 大きなリソースを必要とするなど、レンダリングに時間がかかるstoryは、VRTの対象外にするか、またはdelayを使ってスクリーンショットの撮影を遅らせる。
7. おわりに
この記事ではフロントエンドにおけるテストのトレンドとして、従来のTesting PyramidからTesting Trophyへの緩やかな変遷を取り上げ、モダンフロントエンドのテストのポイントを挙げました。最後にそれらのポイントと親和するChromaticについて、基本的な機能を紹介しました。
Storybookを導入しているチームでは、ChromaticによるVRTの導入と維持は容易です。工数に対し得られるメリットが多く、ROIの高いテストであるため、費用の問題さえなければ多くのチームに適性のあるツールだと言えます。
この記事を書くに当たり、Eight Engineering Unitの青山 修平、鳥山 らいか、Bill One Entryグループの薩田 和弘に助言を貰いました。
*1:クラウド請求書受領サービス「Bill One」が提供するデータ化機能。
*2:Succeeding with Agile: Software Development Using Scrum
*3:誤解されることもありますが、Testing Trophyはバックエンドやマイクロサービスのために提唱されたものではありません。
*4:The Testing Trophy and Testing Classifications
*5:The Testing Trophy and Testing Classifications
*6:もしくは、Componentのユーザーは別のComponentと考えることもできます。その場合でも、呼び出し元のComponentは、クラス名やDOM構造などに関心を持つべきではありませんし、exportされているAPIだけを信頼するべきです。
*7:https://www.thoughtworks.com/content/dam/thoughtworks/documents/radar/2020/05/tr_technology_radar_vol_22_en.pdf