自動テストのスタブ・スパイ・モックの違い

ソフトウェアの自動テストで使う代品オブジェクト、いわゆる「 テストダブル 」の種類についてまとめてみます。 タイトルにはスタブ・スパイ・モックの 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 が利用するコンポーネントにどのようなアクセスがあったかを記録するものです。

コンポーネントへのアクセスというのは OOP でいうと「メソッド呼び出し」のことです。 単に記録するだけではなく、記録した内容について検証を行う機能を兼ね備えている場合もあります。

テスト対象の処理をひととおり実行した後に「メソッドは呼び出されたかどうか」「メソッドは何回呼び出されたか」「メソッドに渡された実引数は何だったか」といったポイントを検証したいときにテストスパイを使用します。 『 xUnit Test Patterns 』の表現を使うなら、テストスパイは SUT から外部への 間接アウトプット(= indirect outputs )を確認するためのものです。

具体的なパターンとしては、本物のコンポーネントを包み込むラッパーとして実装されているパターンや、本物のコンポーネントをまるごと入れ替えるテストスタブに記録機能が付け加えられたパターンとがあります(他にもあるようですが、代表的なものはこの 2 つかと思います)。

ポイント:

  • SUT から他コンポーネントへのアクセスを記録する

モックオブジェクト

モックオブジェクト (あるいは単に モック )とは、 SUT が利用するコンポーネントの代品として動作し、 SUT の動作中にコンポーネントへのアクセスを検証するものです。

コンポーネントへのアクセスというのは、 OOP ではメソッド呼び出しのことです。 SUT が動作しているまさにその最中に「どのメソッドがどういう実引数で呼ばれたか」をチェックします。

テストスパイと同じくモックオブジェクトもスタブの機能を併せ持っていることがあります。

ポイント:

  • SUT から他コンポーネントへのアクセスをリアルタイムに検証する

フェイクオブジェクト

フェイクオブジェクト とは、 SUT が利用するコンポーネントの代品として動作し、本物のコンポーネントと同等の挙動をするものです。

ただし、テストスパイやモックオブジェクトとは異なり、フェイクオブジェクトは「検証」のために使用するものではありません。 フェイクオブジェクトは次のようなときに使用するものです。

  • 本物のコンポーネントが未実装でまだ利用できない
  • 本物のコンポーネントを使うと実データの変更や削除等の望ましくない副作用が発生する
  • 本物のコンポーネントが遅いのでテストでは使いたくない

代表的な例は、テスト用途でのみ生成されてテストが終われば破棄されるインメモリのストレージのゲートウェイクラス等です。

ポイント:

  • 代品として動作する
  • 本物のコンポーネントと同等の挙動をする(本物と差し替えても SUT の挙動には影響を与えない)

(ダミーオブジェクト)

ダミーオブジェクト とは、テスト中で SUT の利用に必要なコンポーネントの代品です。 ただし、ダミーオブジェクトは、スタブ・スパイ・モック・フェイクオブジェクトで挙げた他のものとは異なり、何の機能も備えていません。

ダミーオブジェクトはテスト対象の状況を作り出すのに便利ではありますが、 SUT がダミーオブジェクトを利用して動くわけではないというのがポイントです。 その意味で、『 xUnit Test Patterns 』では「ダミーオブジェクトはテストダブルではない」と説明されています。 この記事でダミーオブジェクトを(ダミーオブジェクト)とかっこ付きで書いているのはそのためです。

ダミーオブジェクトの最も単純な例は「関数の引数の条件を揃えるためだけに渡す実引数」です。

ポイント:

  • SUT の挙動には関係ないけれど SUT の実行には必要なコンポーネントの代品として動作する
  • 挙動を指定したり検証したりする機能は持たない(=厳密な意味でのテストダブルではない)

「スタブ」「スパイ」「モック」の違い

とここまで来るともう答えは出ていますが、タイトルのお話です。 「スタブ」「スパイ」「モック」の違いは以下のとおりです。

  • スタブとは … SUT に特定の挙動をさせるために、 SUT の依存コンポーネントの代品となりあらかじめ指定された挙動をする
  • スパイとは … SUT から別のコンポーネントへのコミュニケーションが想定通りに行われるかどうかチェックするために、 SUT 以外のコンポーネントへのアクセスを記録する(そしてその記録は事後のチェックに利用できる)
  • モックとは … SUT から別のコンポーネントへのコミュニケーションが想定通りに行われるかどうかチェックするために、 SUT 以外のコンポーネントへのアクセスを処理中にチェックする

つまり、スタブは SUT をコントロールするためのもので、スパイとモックは SUT から別コンポーネントへのコミュニケーションをチェックするためのものです。

定番のテストダブルライブラリでは、スパイとモックがスタブの機能を兼ね備えていることもあります。 その場合は次のとおりになります。

  • スタブ: 指定された挙動をする機能
  • スパイ: スタブ + 記録機能
  • モック: スタブ + 処理中の検証機能

上述のとおり、スパイとモックは兄弟のような関係にあります。 どちらも SUT から別のコンポーネントへのコミュニケーションを検証するためのもので、ただそのアプローチ(検証のタイミング)だけが異なります。 どちらかよくわからない場合は、事後に検証を行っていればスパイ、事前に期待値をセットする形で検証を行っていればモック、と考えるとよいでしょう。

表現を少し変えて整理してみます。

間接インプットのセット機能 間接アウトプットのチェック機能
スタブ
スパイ△(備えていることもある)○(記録を通して事後にチェック)
モック△(備えていることもある)○(事前に期待値をセットしてチェック)

というわけで、自動テストのテストダブルの分類とスタブ・スパイ・モックの違いについてでした。

参考