最新版=>フロントエンドのテストについて考える
背景
フロントエンドのテストは、テストランナー、フレームワーク、Node.js、ブラウザ、Selenium、WebDriverなど、登場人物が多い。また、UIと密接に絡むのも特徴である。
これまで社内では、テスト種別によって、それぞれ解決したい事柄が明示的に示されていなかった。それぞれのテスト種別にどんな意味があり、何を目的とするかを明確にすることで、機能に対して、どのようなテストを実装すればよいのか共通認識を持っておくために、この記事を作成した。
フロントエンドのテスト4象限
今回この記事で紹介するテストは、Unit Testing、UI Testing、E2E Testing、Integration Testingの4つがあるが、それぞれ、上のグラフのような関係になっている。これらを考えることで、バランスの取れたテスト環境を確立したい。ポイントとなるのは以下の点である。
-
環境
とあるが、これは、テストを実行する環境というだけで、テスト対象の性質とは無関係である。例えば、Integration Test
では、左下のNode.js環境で実行され得る関数群もブラウザで結合してテストしている。 -
Unit Test
はブラウザでもNode.jsでも実行される。 -
UI Test
は単体でも結合でも利用される。 -
Integration Test
は、フロントエンドアプリケーションでの結合を指している(APIなどはモックする)。 - ブラウザ環境でのテスト≠実行、実装コストが高い
- ブラウザ環境でのテスト≠
E2E Test
-
E2E Test
はたしかにブラウザ環境だが、Selenium(以下総称としてこの言葉を使う)を使用している点で他のテストとは異なる性質を持つ。 - 厳密には、
E2E Test
⊂Integration Test
かもしれないが、ここでは便宜上言葉を分けている。
コンポーネントのテストに仮想DOMを使うか否か
ここはとても悩んだところで、結論としては、後述もしているが、ブラウザで実DOMを描画することにした(※「ブラウザで実DOMを描画≠E2E Test
」です、念のため)。悩んだポイントとして以下のようなものがある。
仮想DOMでのテスト is モダン?
昨今のJavaScriptでは、仮想DOM環境でのテストライブラリがかなり充実している。そんな中、まだ実DOMを使うことを考えると、消耗している気持ちになってくる。
また、Node.js環境は、やはり実行速度が早いというのもある。Karmaのテストは、どれだけ最速化しても、ブラウザが立ち上がる時間、レンダリングの時間などの分、時間が長くかかってしまう。
さらに、これはどちらがいいとかではないのだが、実DOMだと、ブラックボックス
的なテストになる事が多くなる。コンポーネントメソッドの内部までには立ち入らず、ふるまいだけをテストする感じ。対して、仮想DOMだと、ホワイトボックス
的な、ロジックだけに集中したテストも可能になる。
実DOMだとテストできる内容が拡がる?
仮想DOM環境だと、制限されたAPIしか使用できず、どうしても仮想DOMの範疇でしかテストができない。実DOMにはそういった制限がないため、テスト内容に幅をもたせることができる。ただ、仮想DOMのテストで受け持つ範囲、設計は、実DOMのテストとは責務が異なると考えた。仮想DOMでできることだけテストしても十分という方針もあり、また、そのように設計しているプロジェクトも数多くある。
実DOMだとテストの精度が上がる?
実DOMだと、当たり前だが、実際に描画されたDOMを操作するので、テストの精度が上がるのではと考えた。ただし、これも上と同じで、仮想DOMの責務は、レンダリングが含まれないのではという考えがある。また、現状の仮想DOMテストツールを考えると、格段に精度に差があるとは考えにくい。では、やはり仮想DOMのテストだけでいいのだろうか?
実DOMだと、描画を常に目視確認できる
実DOMだと、実際にブラウザにDOMを描画するので、スタイルも合わせて常に目視でコンポーネントを確認できる。Jestのスナップショットテストなどはあるが、UIを確認できるわけではないので、ここはやはり大きいポイントだった。また、コンポーネントごとのテストなので、Atomicに開発することもできる=コンポーネント毎にデザインパーツを作ることが容易になる。これが開発効率に与える影響は大きいと考えた(実際大きかった)。
コンポーネントという責務を考えても、スタイルは閉じた世界に内包されているべきで、最小限のビルドで確認ができるのは重要である。仮想DOMのテストであっても、全ファイルのビルド・再描画を経ずに開発はできるが、スタイル開発ができない。
スタイルの開発なんて、ページごとにやればいいんじゃないの?と思うかもしれない。しかし、例えば、「フォームのバリデーションエラーの文言が崩れている」ことを修正するときに、エラー文言のコンポーネントだけを描画して修正、最後につなぎ合わせて確認、ということができる。また、「バリデーション内容によってエラーの色を変えたい」といった要望があった場合も、コンポーネントのテストで分岐を確認すればいいので、全体の描画をする必要がなく、非常に時間を節約できる。こういった細かい分岐は、手動目視確認や、Integration Test
ではカバーするのが難しい。
実DOMを使うことに
上記を踏まえると、仮想DOMでのテスト実装、設計はありだが、やはり実DOMでのテスト環境があったほうがベターであると考えた。実際は、ベターというよりは、UI Test
がしたいがための消去法的な選択だと思う。しかし、開発を効率化できるのは大きい。
テスト方針まとめ
以上、前置きが長くなったが、先に、結論としての方針を書いておく。
- Unit Testing=>基本はNode.js環境で高速に実行。コンポーネントだけはブラウザ環境を推奨。
- UI Testing=>ブラウザ環境でコンポーネントを描画して行う
- Integration Testing=>ブラウザ環境で親コンポーネントを描画して行う
- E2E Testing=>やらない
テスト方針詳細
以下、詳しく見ていく。
Unit Testing
routine(classやfunction)単位での機能担保ができる。前提として、高速に実行できるというのがある(レガシーコード改善ガイド
では、高速でなければユニットテストではない、とも述べている)。
基本はNode.js環境で、仮想DOMコンポーネントだけは、UI Test
も行うことのできる、ブラウザ環境で実行することを推奨する。
テストランナー、フレームワーク。
- AVA
- Jest
- Karma
- Tape
- Mocha
- Jasmine
Node.js環境で仮想DOMのシミュレーションをしたい場合には以下が使える。
- Enzyme
- ReactTestUtils
UI Testing
ブラウザ環境で、仮想DOMコンポーネントの見た目(スタイル)、挙動の機能担保ができる。
Unit Test
ほど語られることが少ないが、昨今のJavaScriptでは、仮想DOMコンポーネントにスタイルが内包されていることが多く、コンポーネントの組み方もスタイルを意識したものになるため、見た目をテストする環境は重要だと考える。
コンポーネントを、なんらかのブラウザ環境に描画して実装する。
- Karma(rendering DOM to document, most of the time we use
ReactDOM.render
) - Nightwatch(using E2E environment for UI Testing, like capturing screenshots)
Nightwatchは、テスト種別の例としてあげているだけで、推奨はしない。Karmaでなくてもいいとは思うが、コンポーネント単体でのUI Test
環境を整備すると、アプリ全体を描画する必要がなく、AtomicなTDD思想で開発を進められるというのが、UI Test
を導入する最大のメリットだと個人的には思う。具体的な開発プロセスはまた別の記事に書きたい。
Integration Testing
自動
いくつか、あるいはすべてのコンポーネントを組み合わせたテストを流すことができる。
現状は、親コンポーネントをブラウザ環境(Seleniumではない)に描画してテストしている。理由としては、DOMを起点としたアプリケーション全体の結合テストができる、(つなぎこみは)親コンポーネントの責務の一部としても捉えることができるからである。
また、CI上のブラウザ環境であれば、クロスブラウザでのテストを行うのが望ましい。ただし、昨今ではモバイルファーストが多いため、本当にクロスブラウザテストが必要かどうかはコストと天秤にかけて議論の余地はある。
手動
結合テスト。デプロイされたアプリを各環境で手動でテストする。予め作成したシナリオに沿ってテストする。
E2E Testing
Seleniumを使ったブラウザ操作での自動結合テストができる。
ただし、ブラウザ環境でIntegration Test
を正しく実装していれば、わざわざSeleniumを使ってend to endのテストを実装する必要性は低い。なぜSeleniumを使う必要があるのか?ということを考えると自ずと答えは見えてくると思う。
- Nightwatch
- Protractor
まとめ
方針のまとめを再度のせておく。
- Unit Testing=>基本はNode.js環境で高速に実行。コンポーネントだけはブラウザ環境を推奨。
- UI Testing=>ブラウザ環境でコンポーネントを描画して行う
- Integration Testing=>ブラウザ環境で親コンポーネントを描画して行う
- E2E Testing=>やらない
これによって、テスト種別による目的が明確になり、腹落ちしてフロントエンドのテストに向き合うことができる。実際に、これまで行なっていたテスト設計から大きく変更があったというわけではないが、ドキュメント化しておくことには意義があると思う。また、単体と結合、UIとロジック、といった思想的な部分は、プラットフォームによらず、大きく変わることはないと考える。
ツールなどは今後も移り変わり、もっと効率の良い手法がでてくると思っていて、リポジトリを見る限りだと、AVA、Jestあたりの拡張が直近では進みそう、と個人的には考えている。
フロントエンドのテストノウハウがもっと共有されればいいなと思います。