フロントのテストについて調べていると色んなテストの名前が出てくる。
それぞれの言葉の定義や意味について今まで雰囲気で理解してきた部分があるので一度整理してみる。
よく見るテストの名前(俺調べ)
- ユニットテスト
- インテグレーションテスト
- E2Eテスト
- スナップショットテスト
- ビジュアルリグレッションテスト
- クロスブラウザテスト
Testing Trophy
それぞれの言葉の定義について知る前に、react-testing-library
authorのKent C. DoddsのブログにあるTesting Trophyを頭に入れておくと理解しやすい。
- End to End: ユーザーのように振る舞うヘルパーロボットが、アプリをクリックして回り、正しく機能するかどうかを検証すること。「functional testing」や「e2e」と呼ばれることもある。
- Integration: 複数のユニットが調和して動作することを検証すること。
- Unit: 個々の独立した部品が期待通りに動作することを検証すること。
- Static: コードを書いているときに、タイプミスや型崩れを発見する。
Staticに関しては型システムやリンターを利用して、タイプミスやシンタックスエラーを補足するという意味合いらしく、これについてテストコードを作成したりする類のものではなさそう。
図の意味としてはほぼ見ての通りで、Webアプリのテストを4層に分けるとこうなるよ、というもの。
もっと層が多くなるとか少なくなるとかいろんな考え方がありそうだけど、以下この記事ではTesting Trophyに則って整理を進める。
分類と手法
Testing Trophyを元に考えると、記事冒頭に挙げた「よく見るテストの種類」は大きく2つに分かれる。
-
Testing Trophyにおける分類を意味するもの
- ユニットテスト
- インテグレーションテスト
- E2Eテスト
-
具体的なテスト手法を意味するもの
- スナップショットテスト
- ビジュアルリグレッションテスト
- クロスブラウザテスト
手法はどこかの分類に属することになる。
例えばユニットテストは個々の独立した部品が期待通りに動作すること
を検証する、検証の手法(手段)としてスナップショットテストを用いる、みたいなイメージ。
ただし、関数のユニットテストなど手法に〜〜テスト
のような特別な名前が無い場合もある。
また、フロントエンドの文脈では関数に対して行うテストとコンポーネントに対して行うテストで内容が大きく変わるので両方について考える必要がある。
以下、それぞれの言葉の定義や意味を整理していく。
ユニットテスト(Unit Test)
Testing Trophyより引用
個々の独立した部品が期待通りに動作することを検証すること。
テストの実装例を挙げると、関数に対するユニットテストは言うまでもないこういうやつ。
コンポーネントに対するユニットテストはいくつか種類があり、プロダクトによって採用しているものも異なる。
React公式でもコンポーネントに対するテスト手法は二種類言及している。
一つ目はよく見るやつ。コードを一部抜粋。
import React from "react"; import { render, unmountComponentAtNode } from "react-dom"; import { act } from "react-dom/test-utils"; import Hello from "./hello"; it("renders with or without a name", () => { act(() => { render(<Hello />, container); }); expect(container.textContent).toBe("Hey, stranger"); ...
もう一つは後述のスナップショットテスト
。
インテグレーションテスト(Integration Test)
Testing Trophyより引用
複数のユニットが調和して動作することを検証すること。
関数の文脈だと単純に複数の関数が調和して動作することを検証する。
テストの実装例はユニットテスト同様。
コンポーネントの文脈だと少し複雑になる。
React公式では以下のように言及されている。
コンポーネントのテストでは、ユニットテストとインテグレーションテストの区別は曖昧です。フォームをテストする時、そのテストはフォーム内のボタンもテストすべきでしょうか。それともボタンコンポーネント自体が自身のテストを持つべきでしょうか。ボタンのリファクタリングはフォームのテストを壊さないべきでしょうか。
チームやプロダクトに応じて、答えは違ってきます。
ただしKent C. Doddsのブログでは明確に区別しており、サンプルコードも載せている。
<App>
から扱っているので明らかにインテグレーションテストって感じだけど、説明にも書かれてる通り実際はアプリ全体をレンダリングしたりはしないので、そうなってくるとReact公式の通り定義がどんどん曖昧になってくると思う。
この辺はプロジェクト内で共通認識ができていれば良いと思うので、変に言葉の定義にこだわらない方が良さそう。
E2Eテスト(End to End Test)
Testing Trophyより引用
ユーザーのように振る舞うヘルパーロボットが、アプリをクリックして回り、正しく機能するかどうかを検証すること。「functional testing」や「e2e」と呼ばれることもある。
Testing Trophyの定義上は一見コンポーネントに対するテストっぽいが、クリックした結果実行される関数の検証も行うことになるため、関数に対するテストでもあると認識している。
ツールを用いることが多くcypressが有名。
スナップショットテスト(Snapshot Test)
手法の一つであり、コンポーネントに対して行うテスト。
React公式による簡単な説明。
Jest のようなフレームワークでは、toMatchSnapshot / toMatchInlineSnapshot を使ってデータの「スナップショット」を保存することができます。これを使うことで、レンダーされたコンポーネントの出力を「セーブ」しておき、変更がスナップショットへの変更として明示的にコミットされるよう保証できます。
全然わからない。
Jest公式の説明の方がわかりやすい。
スナップショットのテストはUI が予期せず変更されていないかを確かめるのに非常に有用なツールです。
典型的なスナップショット テスト ケースは、UI コンポーネントをレンダリングしてスナップショットを取得し、テストと一緒に保存されている参照スナップショット ファイルと比較します。 2つのスナップショットが一致しない場合テストは失敗します: 予期されない変更があったか、参照するスナップショットが新しいバージョンのUIコンポーネントに更新される必要があるかのどちらかです。
分類としてはユニットテストもしくはインテグレーションテストになる。
ビジュアルリグレッションテスト(Visual Regression Test)
手法の一つであり、コンポーネントに対して行うテスト。
Storybook
のGIFがわかりやすい。
言葉の定義はBrowserStack
のページがわかりやすい。
コードを変更しても、ソフトウェアの視覚的なインターフェースが壊れないかどうかをチェックします。
ビジュアルリグレッションテストでは、コードの変更前と変更後のスクリーンショットを比較することで、コードの変更が実行された後にユーザーに何が見えるかをチェックします。このため、ビジュアルリグレッションテストは、ビジュアルスナップショットテストとも呼ばれます。
分類としてはユニットテストもしくはインテグレーションテストになる。
クロスブラウザテスト(Cross Browser Test)
手法の一つであり、関数に対してもコンポーネントに対しても行うテスト。
ざっくり言うとブラウザ・OSの組み合わせを変えたとしてもちゃんと動きますか?の検証をする。
クロスブラウザテストとは、非機能テストの一種であり、Webサイトにアクセスした際に、意図した通りに動作するかどうかを確認するものです。
例えば、Firefox、Chrome、Edge、Safariなどの一般的なブラウザと、Windows、macOS、iOS、Androidなどの一般的なOSの組み合わせ。
さまざまなデバイス:スマートフォン、タブレット、デスクトップ、ラップトップなどの一般的なデバイスで、ユーザーがウェブサイトを表示し、操作することができます。
ツールは色々あるがcypressでも機能として備わっている。
自分が業務で使用しているDatadogのブラウザテストは録画したブラウザ操作が複数のブラウザで正常に動作するか検証できる。
分類としてはE2Eテストになる。
End
総じて関数のテストに比べてコンポーネントのテストは難しい。
そういう意味ではフロントエンドは高度なテストスキルが求められると言えるのかもしれない。