はじめに
ソフトウェア開発におけるテストの重要性は、エンジニアであれば誰もが理解できると思います。自分もそれなりの年数ソフトウェアエンジニアをやってきて、テストにおける自分の考え方が少し整理されてきた感覚があります。今回は特にテストコードにおけるIntegration Test(IT)とUnit Test(UT)の棲み分けについて言語化します。
テストについては様々な考え方があり、私がこれから書くことに異を唱える方がいることは理解しています。10人いれば10通りの考え方がある世界なので、「こういう考え方もあるんだな」と一歩引いて読んでもらえると嬉しいです。
また、本記事に記したことは執筆時点での私の考えであり、今後学習していく中で考えや意見が変わりうることをご了承くださいませ。
IT
テストコードでITを書く場合、自分は2つのポリシーを持っています。
モックは可能な限り使わない。依存オブジェクトやDBは本物を使う。(ただし外部APIなどのスコープ外のリソースはモック化する)
- ITは本番環境と近い環境で実施したい
- 依存オブジェクトはモック化せずにDIなどで本物を用意し、DBはコンテナやインメモリDBにテスト専用のテーブルを一時的に構築し、テスト終了時に破棄する。
- もちろんコストはかかる。特にDBの準備・破棄は大変。テスト実行時間も延びがちなのできちんと計測してケアする必要がある。
正常系のハッピーパスだけをテストケースに挙げる。異常系やエッジケースはITでは実装しない。
- ハッピーパス=ビジネスシナリオが正常に終わる実行経路
- 上述の通りモックを利用しない方針をとっているので、私の書くITは実行時間が長くなりやすい。そのためITのテストケースはあまり増やしたくない。
- 細かいケースの網羅性は後述のUTで担保する。
UT
UTにおける自分のポリシーは1つです。
正常・異常どちらも網羅的にカバーするテストケースを実装する
- ITでは網羅的なテストケースをあげない代わりにUTで細かいケースを担保する
- UTでは(必要に応じて)モックを利用するので、ITに比べて各テストケースを高速に実行できる。そのためテストケースの量が多くても速度の面で問題になりにくい。
- モックの利用に関しては古典学派・ロンドン学派の違いによって異なってくるが、長くなる&私が深く理解できていないのでここでは割愛。
共通するポリシー
IT, UTどちらでも意識している共通のポリシーです。
Assertionは結果に対して行う。実装の詳細に対してAssertionを行わない。
- ○:関数の戻り値、APIのレスポンスなど
- ×:ログにxxxが含まれていること、DBのSELECT結果がxxxであることなど
- ※アプリケーションコードの設計の都合上こうしたAssertionをせざるを得ないことがあるのは理解してます。
- テストが実装の詳細に依存してしまうと、リファクタリングした際にテストが壊れやすい。アプリの振る舞いは変わっていないのにテストが落ちてしまう(偽陽性)
テスト実行時間は短いほどいい
- 特別な事情がない限り、テストはCIで自動テストとして実行される前提で書かれている。 テストスイートの実行に何十分も要していると開発体験が悪くなる
とはいえ
ここまで自分のポリシーを書いてきましたが、全てを叶えることは難しいです。
私個人の考えをチーム全体が承諾してくれるかどうかはわかりませんし、私の考えよりもっと良い案があるかもしれません。ですが、こうして自分の考え方をスナップショット的に言語化しておくことは大事だと思います。