テストにおけるモックを使う意味

モック(Mock)とは、データベースやAPI、依存するクラスなど、外部機能の代替として単体テストする場合に使うもの。
接続先がまだ無い場合、テストを通すために使用したり、取得する結果を変更してテストする場合にも便利。

しかし、モックをテストを通すためだけ、カバレッジを上げるためだけの「逃げ」として使っていませんか?
恥ずかしながらTDDをやるために、そんなふうに使っていたんです。

テストが通る事もカバレッジを上げる事も重要ですが、そのためだけにモックを使うのはもったいない。
モックとはプログラミングにおける「設計ツール」で単体テストの目的を達成するためのツールだから。

テストの種類

テストには単体テスト、結合テスト、システムテスト などがある

単体テスト

関数、機能単位のテスト

結合テスト

機能と機能を組み合わせたり、データベースやネットワークを繋ぎこんだりするテスト

システムテスト

本番環境と同じような環境とデータを用意して行うテスト

モックの使いどころは単体テスト

モックを使う場面は単体テストである。ほとんどの場合ここでしか使わない。
モックを使うケースを見てみよう

  • ・接続先のAPIやデータベースが未実装だが、こちらの実装を進めたい
  • ・依存するクラスや機能が未実装だが、今の機能を実装したい
  • ・実装している関数から使用する機能の結果を変更してテストしたい

どれも他に「依存」している部分を仮のモノに置き換えるものばかりだ。

単体テストはすべての依存をなくすべき

単体テストとは実装したコードに対するテストだ。
実装したコード自体が思った通りに動くか確認する。それが単体テスト。

そのコードの以外の機能、ネットワーク、データベースやファイルなど、すべての依存を排除してテストするべきで、ローカル環境でコードのみでテストが実行できることが理想である。

テストからモックを依存注入できるようにする

コード単体でテストをするためには、そのコード内で使用している外部機能を依存注入できるように実装する。
テストでは外部機能の代わりにモックを依存注入する。そうやってテストできるようにリファクタリングしていく。

すると、何が起きるか。
外部から切り離されたテストしやすいコードができあがる。テストしやすいコードはメンテナンスしやすく、使いやすい機能に育っていく。
モックを使うと疎結合でテストしやすい実装に自然となっていくはずだ。これがモックを設計ツールだと言った理由である。

結合テスト

先程まで説明したのは関数単位の小さなテストだ。
プログラムは複数の関数や外部機能を使って実現させる。

実際にネットワークやデータベースなどの外部機能を使ってテストしてみなければ、本当に動くか分からない。
ここで、また登場するのが「依存注入」だ。外部機能を用意して実体を依存注入しテストする。
すでに依存注入できるように実装されているはずなので、環境変数やビルドタグ等で実行するテストを切り替え、依存を注入すればよい。

単体テストでモックを使う メリット デメリット

依存注入できるように外部機能とモックを差し替えるられるように実装するには実際めんどくさい。
実体でテストできるなら、それに越したことはない。持続性にコストを払えるかのトレードオフ。
メリット、デメリットを天秤にかけ、プロジェクトに合えば積極的に採用すべきだ。

環境

外部機能を使う場合は環境を用意したり、実装済みである必要がある。モックの場合はコードでモックを用意すればいい。
データベースなどはDockerのおかげで、簡単に環境を用意できるのため実体を使ってテストする事も多いが、その場合は結合テストとして行えばいい。

テスト実行時間

データベースやAPIなど、実体を利用した場合、結果を取得できるまで、多少の時間がかかる。
モックを使った場合は一瞬だ。コード修正のたびにテストは何回も実行するため、実行時間は少ないほうがいい。
CIでテストを実行する場合も実行時間のメリットは大きい。

依存注入

依存注入できるように実装するには、それなりに大変だった。
コード量やファイルは増えるし、採用するアーキテクチャやディレクトリ構造も考えなくてはならなかった。

外部機能を依存注入できるようになると、どんなメリットがあるかは単体テストで説明したが、まだ利点がある。
外部の変更に対応できるようになる。責務が分離されるので、外部機能に変更があった場合に修正する範囲が限定される。
さらに、MySQLをPostgreSQLに変更したり、Webフレームワーク自体を差し替えたりする事も可能だ。

モックを積極的に使ってみての感想

外部機能の代替で思った通りの結果を取得できるから、テストが通るのは当たり前だ。こんな風に思って使っていたのはもったいなかった。
単体テストの目的である「実装したコード自体のテスト」をやりやすく、そう実装するための設計するツールとしてモックと仲良くなれた気がする。

モックしづらい場合、フェイクサーバーを使う。それも難しいなら実体を使えばいい。
モックを使う事が目的ではないので割り切る事も必要。実装したコードが正しく動くか。これが目的だから。

最終的にモックを使って単体テストと結合テストを分離したプロジェクトを眺めていると、なにやら見覚えのある構成になっていた事に気づいた。
特に意識したわけではなく、自然とクリーンアーキテクチャ風な構成になっていたのだ。これには本当に驚いた。

クリーンアーキテクチャすげぇ!