自動テストのスタブ・スパイ・モックの違い
ソフトウェアの自動テストで使う代品オブジェクト――いわゆる「 テストダブル 」の種類についてまとめました。 タイトルには「スタブ」「スパイ」「モック」の 3 つだけをあげていますが、他にも「フェイクオブジェクト」と「ダミーオブジェクト」に言及しています。
お断り
- この記事の説明は書籍『 xUnit Test Patterns: Refactoring Test Code 』( Gerard Meszaros 著)の定義に基づいています。
- このあたりの用語の定義は人や流派によって異なります。この記事の説明が常に正しいわけではありません。
テストダブルの全体像
テストダブルの分類は次のようになっています。
テストダブル
├ テストスタブ
├ テストスパイ
├ モックオブジェクト
├ フェイクオブジェクト
└ (ダミーオブジェクト)
テストスタブ・テストスパイ・モックオブジェクト等のテスト用の道具をまとめてテストダブルと呼びます。 テストダブルとはプログラムの中でオブジェクトや関数やモジュール(以下これらをまとめて「コンポーネント」と呼びます)の代品として動く仮のコンポーネントのことです。
ちなみに、ダブル( double )という単語は多義的で、日本人におなじみの「倍」の他にもたくさんの意味を持っています。 その中には、スタントマン的な「代役」「影武者」、他人にうりふたつの「生き写し」という意味もあり、「テストダブル」の「ダブル」の意味合いはおそらくこのあたりにあるのだと思います。
各テストダブルの意味
テストダブルの下の各分類について順に説明していきます。
- テストスタブ
- テストスパイ
- モックオブジェクト
- フェイクオブジェクト
- (ダミーオブジェクト)
テストスタブ
テストスタブ (あるいは単に スタブ )とは、テスト対象物(= Systems under Test 、以下「 SUT 」)が利用するコンポーネント(= Dependant on Components 、以下「依存コンポーネント」)の代品として動作するものです。 テスト技術者があらかじめ指定したとおりの挙動をします。
テストスタブを利用することで、特定の状況下での SUT の挙動のテストがやりやすくなります。 挙動というのは OOP のパラダイムでは「メソッドの戻り値」や「 public なプロパティの値」のことです。
テストスタブは「 SUT の依存コンポーネントが仮にこんな挙動をしたら SUT はこういう挙動をするはず」というテストにおける「仮にこんな挙動をしたら」の部分を実現するためのものです。 『 xUnit Test Patterns 』の表現を使うなら、テストスタブは SUT に 間接インプット (= indirect inputs )を与えるためのものです。
テストスタブ →(間接インプット)→ SUT
ポイント:
- 特定の状況下での SUT の挙動を確認するために SUT の依存コンポーネントの代品として動作する
- あらかじめ指定されたとおりの挙動をする
テストスパイ
テストスパイ (あるいは単に スパイ )とは、 SUT の依存コンポーネントにどのようなアクセスがあったかを記録するものです。
コンポーネントへのアクセスというのは OOP では「メソッド呼び出し」のことです。 単に呼び出しを記録するだけではなく、記録した内容について検証を行う機能を兼ね備えていることもあります。
テストスパイは、テスト対象の一連の処理をひととおり実行した後に「メソッドは呼び出されたかどうか」「メソッドは何回呼び出されたか」「メソッドに渡された実引数は何だったか」といったポイントを検証したいときに使用します。 『 xUnit Test Patterns 』の表現を使うなら、テストスパイは SUT から外部への 間接アウトプット(= indirect outputs )を確認するためのものです。
SUT →(間接アウトプット)→ テストスパイ
具体的なパターンとしては、本物のコンポーネントを包み込むラッパーとして実装されているものと、本物のコンポーネントをまるごと入れ替えるテストスタブに記録機能が加わったものとがあります(他にもあるようですが、代表的なものはこの 2 つかと思います)。
ポイント:
- SUT から他コンポーネントへのアクセスを記録する
モックオブジェクト
モックオブジェクト (あるいは単に モック )とは、 SUT の依存コンポーネントの代品として動作し、リアルタイムにコンポーネントへのアクセスを検証するものです。
基本的な機能はテストスパイと同じですが、テストスパイが対象の処理が終わった後に検証を行うのに対し、モックオブジェクトは対象の処理の途中にリアルタイムに検証を行います。 また、モックオブジェクトもテストスパイと同じくスタブの機能を併せ持っていることがあります。
SUT →(間接アウトプット)→ モックオブジェクト
ポイント:
- SUT から他コンポーネントへのアクセスをリアルタイムに検証する
フェイクオブジェクト
フェイクオブジェクト とは、 SUT の依存コンポーネントの代品として動作し、本物のコンポーネントと同等の挙動をするものです。
ただし、テストスパイやモックオブジェクトとは異なり、フェイクオブジェクトは「検証」のために使用するものではありません。 フェイクオブジェクトは次のようなときに使用するものです。
- 本物のコンポーネントが未実装でまだ利用できない
- 本物のコンポーネントを使うとデータの変更や削除等の望ましくない副作用が発生する
- 本物のコンポーネントを使うとテストが大幅に遅くなる
フェイクオブジェクトの代表的な例は、テスト中のみ使われれるインメモリのストレージや外部の API に対応するゲートウェイクラスです。
ポイント:
- 代品として動作する
- 本物のコンポーネントと同等の挙動をする(本物と差し替えても SUT の挙動には影響を与えない)
(ダミーオブジェクト)
ダミーオブジェクト とは、テスト中で SUT の利用に必要なコンポーネントの代品です。 ただし、ダミーオブジェクトは、スタブ・スパイ・モック・フェイクオブジェクトで挙げた他のものとは異なり、何の機能も備えていません。
ダミーオブジェクトはテスト対象の状況を作り出すのに便利ではありますが、 SUT がダミーオブジェクトを利用して動くわけではないというのがポイントです。 その意味で、『 xUnit Test Patterns 』では「ダミーオブジェクトはテストダブルではない」と説明されています。 この記事でダミーオブジェクトを(ダミーオブジェクト)とかっこ付きで書いているのはそのためです。
ダミーオブジェクトの最も単純な例は「関数の引数の条件を揃えるためだけに渡す実引数」です。
ポイント:
- SUT の挙動には関係ないけれど SUT の実行には必要なコンポーネントの代品として動作する
- 挙動を指定したり検証したりする機能は持たない(=厳密な意味でのテストダブルではない)
「スタブ」「スパイ」「モック」の違い
とここまで来るともう答えは出ていますが、タイトルのお話に戻ります。 「スタブ」「スパイ」「モック」の違いは以下のとおりです。
- スタブとは … SUT の挙動をコントロールするために、 SUT の依存コンポーネントの代品となりあらかじめ指定された挙動をする
- スパイとは … SUT から別のコンポーネントへのコミュニケーションが想定どおりに行われるかどうかチェックするために、 SUT 以外のコンポーネントへのアクセスを記録する(そしてその記録は事後のチェックに利用できる)
- モックとは … SUT から別のコンポーネントへのコミュニケーションが想定どおりに行われるかどうかチェックするために、 SUT 以外のコンポーネントへのアクセスを処理中にチェックする
つまり、スタブは SUT をコントロールするためのもので、スパイとモックは SUT から別コンポーネントへのコミュニケーションをチェックするためのものです。
ただし、定番のテストダブルライブラリでは、スパイとモックがスタブの機能を兼ね備えていることもあります。 その場合は次のとおりになります。
- スタブ: 指定された挙動をする機能
- スパイ: (スタブの機能) + 記録機能
- モック: (スタブの機能) + 処理中の検証機能
上述のとおり、スパイとモックは兄弟のような関係にあります。 どちらも SUT から別のコンポーネントへのコミュニケーションを検証するためのもので、ただそのアプローチ(検証のタイミング)だけが異なります。 どちらかよくわからない場合は、事後に検証を行っていればスパイ、事前に期待値をセットして検証を行っていればモック、と考えるとよいでしょう。
表現を少し変えて整理します。
間接インプットのセット機能 | 間接アウトプットのチェック機能 | |
---|---|---|
スタブ | ○ | ✕ |
スパイ | △ (備えていることもある) | ○ (記録を通して事後にチェック) |
モック | △ (備えていることもある) | ○ (事前に期待値をセットしてチェック) |
というわけで、自動テストのテストダブルの分類とスタブ・スパイ・モックの違いについてでした。