Moshe Kolodny氏が最近SafeTestを発表した。SafeTestは、フロントエンドのウェブテストに対する斬新なアプローチであると説明されている。SafeTestは、テストランナー、ブラウザ自動化ライブラリ、UIフレームワーク、依存性注入機能を統合し、従来のUIテスト手法の問題点を緩和する。SafeTestは現在、Netflixで使用されている。
Kolodny氏はリリースノートの中で、UIテストに対する2つの主なアプローチ、すなわち単体テストとエンドツーエンドテストは、どちらもトレードオフの関係にあると説明している。
ユーザーインターフェースがコンポーネントベースのフレームワーク(React、Vue、Angularなど)で記述されるのが一般的になると、UIの単体テストがコンポーネントテストに発展することが多くなる。コンポーネントをフレームワークに結びつけるには、そのフレームワークがアドホックなテスト機能を提供する必要がある(例えば、reactのreact-dom/test-utilsの
act
メソッド、AngularのTestBed
API)。コンポーネントが大きく複雑になるにつれて、コンポーネントの依存関係(UIフレームワーク、コンポーネントの子や小道具、DOM、ネットワーク、雑多な副作用関数、構成設定など)が膨れ上がった。そのため、単体テスト(依存関係から切り離してユニットをテストすること)は、ますます骨の折れる作業となった。このような単体テストは、開発者に内部実装の詳細をテスト内で参照させることが多く、コンポーネントの実装が変更されるとテストが不当に失敗する結果となった。
その結果、フロントエンド開発者は近年、浅いレンダリング(ReactのEnzymeライブラリなど)のコンポーネントから、完全なDOMレンダリング(dom-testing-libraryスイートなど)へと移行している。コンポーネントは、アプリケーション全体の仕様のごく一部を担っているため、実際のユーザーと同じようにDOMをインスツルメンテーションする(つまり、DOM上でのユーザーのアクションをモックする)ことで、これらの仕様をテストするという考え方がある。
DOMの高品質なモック(js-domなど)は、主にNode.jsのような非ブラウザ・ランタイムで使用するために存在するが、それでもウェブ標準の全リストを実装できていないため、すべてのケースでブラウザの動作を完全に再現できているわけではない。
Kolodny氏は以下のように説明している。
テストのセットアップを書くのは簡単だが、ページ上の動作を引き起こすのに必要なイベントを書くのは難しいです。例えば、派手な
を表示するのは、単に
fireEvent.click('select')
を呼び出すほど単純ではありません。js-domが実際のブラウザと完全に一致しないからです。同じラインで、スマートなにテキストを入力する方法を見つけ出すことも、似たような戦いがあります。これを実現するための正確な呪文を見つけ出すのは難しく、脆弱です。ブラウザーを開いて何が起こっているのか見ることができないので、なぜ動かなくなったのかをデバッグするのも難しいです。
これにより、ブラウザをモックせずにコンポーネントをテストし、代わりにブラウザのヘッディング版やヘッドレス版( Microsoft社のPlaywrightや Google社のPuppeteerなど)を使うようになった。
極端な話、すべてのモックを取り除くと、Kodolny氏が言及した2番目のテスト方法、エンドツーエンドのテストにつながる。Kodolny氏は、関連するトレードオフについて次のように警告している。
CypressやPlaywrightのようなE2Eテストは、実際のアプリケーションをテストするのに適しています。これらは実際のブラウザを使用し、実際のアプリケーションに対して実行されます。Zインデックスの問題などをテストできます。
しかし、コンポーネントを分離してテストする機能がないため、E2Eテストを行うためにStorybookの隣接ビルドを持っているチームもあります。
もう一つの問題は、異なるテストフィクスチャをセットアップするのが難しいことです。例えば、管理者ユーザにはページ上に編集ボタンがあり、通常のユーザには編集ボタンがないことをテストしたい場合、異なる結果を返すように認証サービスをオーバーライドする方法がいくつか見つかるだろう。
同様に、Cypress や Playwright のコンポーネントテストはアプリの実際のインスタンスに対して実行されないため、OAuth のような外部サービスに依存するコンポーネントテストは不可能です。
SafeTestは、前の2つの方法(単体テストとエンドツーエンドテスト)の長所を組み合わせた第3の方法だ。SafeTestは、実際のブラウザ(スクリーンショット、ビデオ録画と再生、トレース・ビューア)を活用しながら、ブラウザ以外の依存関係をモックできる。
SafeTestは、ネットワークやウェブサービスなどの外部依存関係をモックまたはスタブしたりするために、他のライブラリを活用できる。Playwright では、例えばネットワークレイヤーをスタブできる。内部依存関係(静的関数、値、API レスポンスなど)については、SafeTest はモック/スパイ API と、適切にオーバーライドと呼ばれる依存関係注入メカニズムを提供する。
Hacker Newsのあるユーザーは、SafeTestはReactに適切な依存性注入メカニズムをもたらすことができるかもと強調している。
ReactのためのDI(依存性注入)に焦点を当てた一般的なフレームワークであるべきでしょうか?そうすれば、オーバーライドをより有機的なものにすることが可能で、単なるテスト以上の利点があります。
別のHacker Newsユーザは、パラメータという形で暗黙の依存関係を明示することで、依存性注入のニーズを緩和できると意見を述べた。
残念ながら、この答えはツールの中にあるわけではありません。 ソフトウェアの設計にあります。 何かをコントロールする必要があるのなら、それはコントロール可能でなければなりません。通常、これはプッシュを意味します。Reactコンポーネントでは、これはpropsを意味します。オプションのプロップかもしれませんが、そのプロップがあれば、このコンポーネントを制御できます。コンポーネントがプッシュで制御できるようになれば、コンポーネントをレンダリングしているページもプッシュで制御できるようになります。どのようにページにプッシュしますか?クエリ文字列パラメータが一番簡単です。
Storybookはそれを可能にしてくれます。
さらに別のユーザーは、SafeTestが見慣れない方法ではあるものの、本当の問題点に対処していることに賛成している。
解決策の概要は本当に興味深く、貴重なものだと思います!UIを単体テストする際の特殊性は、回避するのが難しいものです。そして、個々のUIをテストする文脈におけるe2eテストの問題も、まったく妥当なものです。
少なくとも当初は、オーバーライドがどのように機能するのかが気になっていました。あるデータを注入するための解決策であることは理解できますが、このツールでテストを可能にするためだけに、コンポーネント内にテスト専用のコードを書くというアイデアは好きではありません。とはいえ、過去に同じようなことをしたことがあり、選択肢がなくなってしまったとき、これはかなりきれいに見えます。
SafeTestは、MIT ライセンスのオープンソースソフトウェアである。技術的な詳細やコードサンプルについては、リリースノートで確認できる。