React の テスト方針を調べた
今回は「Reactでテスト環境を作ろうっ!」と思ったのですが、そもそもReactのテストってどうするの?どんなツールを使うの?ということで、まずはReactのテストについて調べてみました。
Reactのテスト
Vitest
Vue.jsやViteの開発者が作っている高速で動作するテスティングフレームワークです。
2022年2月16日現在 v0.3.5でまだ開発版です。
GitHubにも「免責事項:Vitestはまだ開発中であり、安定したものではありません。実運用での使用はお勧めしません。」とあります。・・・残念😭
Jest + React Testing Library
Reactの公式サイトによると Jest + ReactTestingLibrary が推奨のようです。
JavaScript テスティングフレームワークです。 React コンポーネントをテストすることができるツールセットReactでのテスト方針
React公式で推奨されている React Testing Library は、
このライブラリを開発した Kent C. Dodds氏の考えた「Testing Trophy」というテストコンセプトを実現しやすいように作られています。
React Testing Libraryを採用するのなら、「Testing Trophy」に従うとよさそうです。
Testing Trophy とは?
コチラがとてもわかりやすかったです。
【フロントエンド】コンポーネント指向(React, Vue)のテスト方針
「Testing Trophy」は、各テストの書くべき総量をトロフィーの体積の比率で表しています。
体積の大きいテストほど多く書くことを示していて、インテグレーションテストに重点が置かれています。
従来のピラミッド型は、各テストの書くべき総量をピラミッドの面積の比率で表しています。
面積の大きいテストほど多く書くことを示していて、ユニットテストに重点がおかれています。
従来のピラミッド型のテストは品質保証を考慮していないため、開発工数が少なく実行速度が速いユニットテストに重点が置かれました。
しかし品質保証を加味すると、トロフィー型のインテグレーションテストに最も重点を置いたテストコンセプトが提唱されたようです。
各テストの種類
▶ E2E(機能テスト)
End to Endの略。
ユーザーのようにふるまうヘルパーロボットが、アプリをクリックして回り、正しく機能するかどうかを検証するテストです。
主にcypressやSeleniumなどを使用してテストします。
▶ Integration(結合テスト)
複数のユニットが調和して動作することを検証するテストです。
▶ Unit(単体テスト)
個々の独立した部品が期待通りに動作することを検証するテストです。
▶ Static(静的テスト)
コードを書きながら誤字・脱字をチェックするテストです。
TypeScriptやESLintのような静的型付けとリンティングのツールで行います。
ReactでIntegrationテストとはどこのテストのこと?
結局のところ、ReactでIntegrationテストをするとは、具体的にどこをテストすればいいのでしょうか?
こちらはKent氏のブログからの抜粋です。
コンポーネントが単独で動作することを確認するユニットテストをいくつか用意することは悪いことではありませんが、それらが適切に連携して動作することも確認しなければ意味がありません。そして、それらが適切に連携して動作することをテストすることで、わざわざ単体でテストする必要がなくなることが多いことに気づくでしょう。
統合テストは、信頼性とスピード・コストのトレードオフのバランスをうまくとっています。そのため、ほとんどの労力をそこに費やすことが推奨されます (すべてではありませんが)
別の日のブログからの抜粋です。
私はいつも、エンドツーエンドのテストとは、モックを一切使わずに(あるいはより現実的には「できるだけ使わずに」)物事が動くことを検証しようとする場所だと考えています。
そのため、私自身のコードに関するテストを「ユニット」と「インテグレーション」のいずれかに分類することになりました。私は「ユニット」とは、ロジックを含む単一の関数、クラス、オブジェクトのことだと考えています。そこで、(大雑把に)こんな風に分類することにしました。
私自身の定義では、Testing Libraryは、個々のReactコンポーネント(ユニットテスト)、MSW経由でモックしたHTTPリクエストによるページ全体(統合テスト)、ごく少数のモックによるアプリ全体(エンドツーエンドテスト)、必要なら個々のReactフック(低レベルユニットテスト)さえテストするために使うことができます。
つまり、個々のコンポーネントのテストはUnitテストで、ページ全体のテストをIntegrationテストと定義されているようです。
小さな個々のコンポーネントをテストするのではなく、個々のコンポーネントが連携して動作するようなコンポーネントに、テストの労力を費やすことを推奨されています。
また、エンドユーザーが知らないことや気にしないこと(実装の詳細)をテストしないこと、ユーザーによって操作される部分のテストに重点を置くことを強く主張されています。
テストしているコンポーネントの内部など、実装の詳細をテストしないことを推奨しています(それでも可能ではありますが)。このライブラリの指針は、あなたのウェブページがユーザーによってどのように操作されるかに酷似したテストに重点を置くことを強調しています。
そのため、以下のような実装は避けるようにしましょう。
コンポーネントの内部状態
コンポーネントの内部メソッド
コンポーネントのライフサイクルメソッド
子コンポーネント
実装の詳細をテストしてしまうと、リファクタリングでテストが壊れてしまう可能性があります。
リファクタリングは、既存の動作は変更せずにその実装を変更することなので、実装の詳細のテストが壊れてしまうのは当然かもしれません。
実装の詳細をテストしてしまうと、テストのせいでリファクタリングを制限してしまったり、リファクタリングでコードを変更するたびにテストを修正することになり、テストの意味が薄れてしまいます。
まとめ
とりあえずはテストを書きましょう。
納期までの限られた時間の中で、すべてのテストを完全に書くことはできませんし無意味です。
カバレッジ100%を目指すのではなく、ユースケース100%を目指したIntegrationテストに重点をおくことで、安心してリファクタリングに臨める環境を目指してみます。
Kent C. Dodds氏のブログ
フロントエンドアプリケーションの静的テストとユニットテストと統合テストとE2Eテスト
テストを書く。多すぎず、少なすぎず。ほとんどは結合テスト
実装の詳細テスト
テストユーザーを排除する
参考サイト
Discussion