フロントエンド開発におけるテスト自動化について
はじめに
フロントエンド開発において、どこに/いつ/どうやってテストを書いたらよいのかまとめていきます テスト対象について
関数やクラス
その他
テストの種別/分類
テストには色々な分類の仕方があると思います
実装した関数やクラスなどに対して他の要素とは独立してテストを行う
副作用のない関数やクラスなどはモックやスタブを必要とせず容易に独立してユニットテストが記述できます SOLID原則などに従い、独立または効果的にユニットテスト可能な関数やクラスの比重を増やせると理想的ではあると思います ただし、実際に開発を行っているとなかなかそれが難しい場合も多いと思うので、まずはインテグレーションテストを中心にテストを増やしていくのも現実的なのではないかと思います
複数の要素(関数, クラス, ミドルウェア, 外部サービスなど)などを組み合わせてテストし、それらがきちんと意図した通りに連携されていることを確認します
フロントエンド開発においていくつか例を挙げると、以下のようなテストなどがインテグレーションテストと呼べるのではないかと思います 複数の関数やクラス、モジュールなどを組み合わせて、それらがきちんと連携されていることをテストする
自動テストがまだ整備されていないコードベースに対してテストを導入したい場合は、ユニットテストよりもまずはインテグレーションテストを優先して整備していくと効果的な場面も多いのではないかと思います
インテグレーションテストが増えてきたら、カバレッジなどを計測しつつ、ユニットテストがしやすい構造にコードを少しずつ置き換えていくと安全だと思います
もし置き換えていく段階でカバレッジが低下した場合は、どこかにバグなどが埋め込まれてしまっている可能性があるので注意するとよさそうです
テストコードが実装の詳細に強く依存していると、テストコードが意図せずして壊れてしまい、テストの信頼性が低下してしまう可能性があるためです 導入の方法としては、以下のようにすると効果的だと思います
1. バグが発生した際に、まずそのバグを再現するための失敗するテストコードを書きます。
2. 次にそのテストコードを実行してみて、実際にテストが失敗することを確認します。
3. 最後にその失敗しているテストコードがパスするようにバグを修正します。
こうすることで、テストコードの実行がCIなどで自動化されていれば、同じ原因でバグが再発することを防止できます ただし、実際には常にこの方法でテストをうまく書けるとは限らない場面も多いと思うため、必要に応じて後からテストを導入することなども検討するとよいと思います
また、このテストがあることにより、バグの再発が防止されます
テストダブルとはモック, スタブ, スパイなどのテストにおいて本物のオブジェクトの代替として機能するオブジェクトなどのことです どれを使うべきか?
フェイクとは、外から見た際にあたかも本物のオブジェクトであるかのように振る舞うオブジェクトのこと
フェイクを実装する際は、契約による設計における事前条件・事後条件・不変条件が本物のオブジェクトとフェイクとで一致していると理想的だと思います 例としては、バックエンド開発における永続化レイヤー(Repository, Data Access Objectなど)のインメモリ実装などがそれにあたります
上記の書籍では、フェイクに対してもテストを記述することが推奨されています
モック・スタブの乱用について
パッケージのスタブについて
個人的な意見としては、これらの機能は非常に便利ではありますが、どうしてもそれを使わないとテストが難しいというケースを除いて使用を避けるのがよいと考えています
これらの機能を使うと実装の詳細に強く依存した信頼性の低いテストに陥ってしまうというのが理由です (ホワイトボックステスト) 「テスト対象がどのパッケージに依存しているか?」というのは実装の詳細であり、テストコードがこれらについての知識を持ってしまうことはあまり理想的ではないと考えます
理想としてはこれらの機能を使わずとも単独でユニットテストが書けるようにすることや、mswなどの別の手段を用いてスタブをすることが理想的ではないかと思います HTTPリクエストのスタブ
ユニットテストやインテグレーションテストを記述する際に、実際のAPIサーバーに対してHTTPリクエストを送信したくない、というケースは頻繁に発生するのではないかと思います
こういった目的を解決するために様々なパッケージが存在します
理由はfetch-mockやmoxiosなどは特定のHTTPクライアント(例: fetch, axios)と強く結合していることが挙げられます 例えば、moxiosを採用していた場合、もし「HTTPクライアントをaxiosからfetch()へ移行したい」というようなケースが生じた場合、テストコードの大幅な書き換えが発生する恐れがあります mswやnockなどのように特定のHTTPクライアントに依存しない方法であれば、そういったケースにおいても問題なく機能し続けてくれます 個人的には、この手法に従いスタブの使用をできるだけ抑えると理想的だと思います
好みや設計方針などに応じて使い分けるとよいと思います
結合度の低下や凝集度を高めるのに役立つ手法について
こういったコードを書く方法についてはSOLID原則などについて学ぶと理解が深まるのではないかと思います どういった場面でテストを書くとよいか