スナップショットテスト
スナップショットのテストはUI が予期せず変更されていないかを確かめるのに非常に有用なツールです。
モバイルアプリでの典型的なスナップショットのテストケースでは、UIコンポーネントをレンダリングし、スクリーンショットを撮り、テストと一緒に保管している参照イメージと比較します。 2つの画像が一致しない場合テストは失敗します: 予期されない変更があったか、新しいバージョンのUIコンポーネントに更新される必要があるかのどちらかです。
Jestにおけるスナップショットテスト #
Reactコンポーネントをテストする場合には同様のアプローチをとる事ができます。 アプリケーション全体の構築が必要となるグラフィカルなUIをレンダリングする代わりに、シリアライズ可能なReactツリーの値を素早く生成するテスト用レンダラーを利用できます。 シンプルなLink componentのテスト例について考えてみましょう:
import React from 'react'; import Link from '../Link.react'; import renderer from 'react-test-renderer'; it('renders correctly', () => { const tree = renderer.create( <Link page="http://www.facebook.com">Facebook</Link> ).toJSON(); expect(tree).toMatchSnapshot(); });
このテストを初めて実行した時は、Jestは次のような スナップショット ファイル を作成します。
exports[`renders correctly 1`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]} > Facebook </a> `;
生成されるスナップショットはコードの変更に追随し、かつコードレビューのプロセスの一部としてレビューされるべきです。 Jestはスナップショットをコードレビュー時に人間が読める形式にするために pretty-formatを利用します。 初回以降のテストでは、Jestは単にレンダリングされた出力と以前のスナップショットとの比較を行います。 それらが一致すれば、テストを通過します。 一致しなければ、テストランナーが修正されるべきバグをコード中に発見したか、実装が変更されたためスナップショットを更新する必要があるかのどちらかです。
スナップショットのテストのしくみ、およびそれを作成した理由の詳細については、 release blog postで読むことができます。 スナップショットテストを使用するに当たって良い感覚を身につけるために このブログ記事を読むことをお勧めします。 Jestでスナップショットテストを行うこの eggheadの動画 も観ることをお勧めします。(訳注: egghead. ioというJavaScript学習サイトの事を指していると思われます)
スナップショットの更新 #
バグが混入した後でスナップショットテストが失敗したときは簡単に目星がつきます。 テストが失敗したら、その原因箇所に向かって問題を修正し、スナップショットテストが再びパスすることを確認すればよいのです。 ここで、意図的な仕様変更によりスナップショットテストが失敗するケースについて議論しましょう。
このような状況はたとえば以下の例のLinkコンポーネントが指すアドレスを意図的に変更した場合に起こります。
// Updated test case with a Link to a different address it('renders correctly', () => { const tree = renderer.create( <Link page="http://www.instagram.com">Instagram</Link> ).toJSON(); expect(tree).toMatchSnapshot(); });
このケースではJestは以下のような結果を出力します。
異なるアドレスを指すようにコンポーネントを更新したのですから、このコンポーネントのスナップショットに変更があると予想するのが妥当でしょう。 更新されたコンポーネントのスナップショットは今やこのテストで生成されたスナップショットと一致しないので、スナップショットのテストケースは失敗します。
これを解決するには、生成したスナップショットを更新する必要があります。単純にスナップショットを再生成するように支持するフラグを付けてJestを実行するだけでできます。
jest --updateSnapshot
上記のコマンドを実行することで変更を受け入れることができます。 お好みで一文字の -u
フラグでもスナップショットの再生成を行うことができます。 このフラグは失敗する全てのスナップショットテストのスナップショットを再生成します。 意図しないバグにより追加されたスナップショットテストの失敗があれば、バグが混ざった状態でスナップショットを記録することを避けるためにスナップショットを再生成する前にバグを修正する必要があります。
再生成されるスナップショットを限定したい場合は、 --testNamePattern
フラグを追加して指定することでパターンにマッチするテストのみスナップショットを再生成することができます。
この機能を試すにはsnapshot example リポジトリをクローンして Link
コンポーネントを変更してJestを実行してみて下さい。
テストは確定的なものであるべき #
テストは確定的なものであるべきです。 つまり変更がないコンポーネントに対して同じテストを複数回実施しても毎回同じ結果が得られるべきなのです。 生成したスナップショットがプラットフォームに固有のものやその他の非確定的なデータを含まないように努めなければなりません。
例えばDate.now()
を利用する Clock コンポーネントがあれば、このコンポーネントから生成されるスナップショットは テストケースが実行されるごとに異なるでしょう。 このケースでは Date.now() メソッドをモックすることでテストを実行するごとに一貫した値を返すようにできます。
Date.now = jest.fn(() => 1482363367071);
これで スナップショットテストを実行するごとに、Date.now()
は一貫して1482363367071
を返すようになりました。 これにより、いつテストを実行したかに関係なく、このコンポーネントに生成されるスナップショットは同じ結果となります。
スナップショットは継続的インテグレーションシステム(CI) では自動的に生成されない #
Jestバージョン20では、明示的に --updateSnapshot
を指定しない限りCIシステムでJestを実行してもJest内のスナップショットは生成されません。 全てのスナップショットはCI上で実行されるコードの一部であることが期待され、新しいスナップショットは自動的にパスしているはずなので、CIシステム上のテストをパスするか確認するべきではないのです。 全てのスナップショットをコミットしてバージョン管理することをお勧めします。
よくある質問 #
スナップショットファイルはコミットする必要がありますか? #
はい、スナップショットがカバーするモジュールとテストと共にすべてのスナップショットファイルはコミットされるべきです。 Jestの他のアサーションの値と同様に、スナップショットはテストの一部とみなされるべきです。 実際、スナップショットが指定された時点でのソースモジュールの状態を表すものなのです。 こうしてソースモジュールが変更された場合、Jestは以前のバージョンから変更があったことを見分けられるのです。 コードレビューにおいてレビュアーが加えられた変更をより理解しやすくなるたくさんの追加のコンテクストを提供するものでもあります。
スナップショットテストはReactコンポーネントでのみ利用できますか? #
React と React Nativeコンポーネントはスナップショットテストを行うのに良いユースケースです。 しかしスナップショットは任意のシリアライズ可能な値をキャプチャでき、出力が正しいかをテストするという目的に対していつでも利用できるべきです。 JestのリポジトリにはJest自身のテスト結果の例や、アサーションのライブラリ、そしてコードベースの様々な部分におけるログメッセージも同様に含まれています。 Jestのリポジトリのsnapshotting CLI output の例を参照してください。
スナップショットテストとビジュアルの回帰テストの違いは何ですか? #
スナップショットテストとビジュアルの回帰テストはUIをテストする2つの独立した方法であり、目的が異なります。 ビジュアルの回帰テストツールはwebページのスクリーンショットを取得して出力された画像をピクセル単位で比較します。 スナップショットテストにおいてはシリアライズされた値をテキストファイルに格納して、異なるアルゴリズムで比較します。 考慮すべき異なるトレードオフがあり、私達がスナップショットテストを作成したことの理由はJest blogで挙げています。
スナップショットテストは単体テストを代替するものですか? #
スナップショットテストはJestに含まれる20以上のアサーションの1つに過ぎません。 スナップショットテストのねらいは既存の単体テストを代替することではなく、追加のテスト結果を提供してテストにおける作業負担を減らすことです。 一部のシナリオではスナップショットテストは特定の機能セット(例: Reactコンポーネント)における単体テストの必要性を取り去る可能性がありますが、並行して利用することもできます。
生成されたファイルのサイズと処理速度についてのスナップショットテストのパフォーマンスはどうですか? #
Jestはパフォーマンスを念頭に置いた修正を実施し続けており、スナップショットテストも例外ではありません。 スナップショットはテキストファイルに保管されるので、テストは高速で信頼性が高いものになります。 Jestは toMatchSnapshot
マッチャを呼び出す各テストファイルごとに新しいファイルを生成します。 スナップショットのサイズはかなり小さく: 参考までにJestのコードベースそのもののスナップショットファイルの総合計を例にすれば、300KB未満です。
スナップショットファイル内での競合を解決するには? #
スナップショットファイルは対象とするモジュールの現在の状態を表すものでなければなりません。 したがって、2つのブランチをマージしてスナップショットファイル内での競合に出くわしたなら、手動で競合を解決するかJestを実行してスナップショットを更新して結果を確認することができます。
スナップショットテストにテスト駆動開発の原則を適用することはできますか? #
手動でスナップショットを作成することもできますが、大抵はやりやすいものではありません。 スナップショットは最初期の段階でコード設計の手引きとなるよりも、テスト対象のモジュールの出力が変更されたかを分かりやすくするものです。
コードカバレッジはスナップショットテストでも機能しますか? #
はい、他のテストと同様です。