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

ソフトウェアの自動テストで使う代品オブジェクト――いわゆる「 テストダブル 」の分類についてまとめてみたいと思います。 タイトルには「スタブ」「スパイ」「モック」だけをあげていますが、他に「フェイクオブジェクト」と「ダミーオブジェクト」にも言及しています。

お断り

  • この記事での説明は書籍『 xUnit Test Patterns: Refactoring Test Code 』(著者: Gerard Meszaros )の定義をベースにしています。
  • このあたりの用語の意味合いは人によって異なるので、ここで述べる説明がいつでも必ずあてはまるわけではありません。
  • ですので、これらの用語に出会ったときは、各用語がどのような意味合いで使われているのかを最初に確認しておくのが吉です。

テストダブルの全体像

分類は次のようになっています。

  • テストダブル
    • テストスタブ
    • テストスパイ
    • モックオブジェクト
    • フェイクオブジェクト
    • (ダミーオブジェクト)

ルートにある「テストダブル」とは、プログラムの中でオブジェクトや関数・モジュール(以下「コンポーネント」)の代品となるものの総称です。 テストダブルはその目的・機能によって分類されそれぞれに名前が付けられています。

ちなみに、テストダブルの「ダブル」( double )という単語には日本人におなじみの「倍」という意味のほかにもたくさんの意味があるそうです。 スタントマンのような「代役」「影武者」、他人にうりふたつの「生き写し」という意味もあって、「テストダブル」における「ダブル」の意味合いはこのあたりにあるのでしょう。

各テストダブルの意味

テストダブルの下にある各分類について上から順に説明していきます。

  • テストスタブ
  • テストスパイ
  • モックオブジェクト
  • フェイクオブジェクト
  • (ダミーオブジェクト)

テストスタブ

「テストスタブ」(あるいは単に「スタブ」)とは、テスト対象物(= Systems under Test )(以下「 SUT 」)が利用するコンポーネント(= Dependant on Components )の代品として動作し、あらかじめ指定された挙動を行うものです。

テスト技術者がスタブの挙動を指定して、テストしたいパターンの処理が行われるように SUT をコントロールします。 「挙動」とは、 OOP のパラダイムにおいてはメソッドの戻り値や public プロパティの値のことを指します。

テストスタブは「 SUT が利用するコンポーネントが仮にこんな値を返したらこうなるはず」の「仮にこんな値を返したら」を実現するためのものです。 『 xUnit Test Patterns 』の用語でいうなら、 SUT に対して「間接インプット」(= indirect inputs )を与えるためのものと言えます。

ポイント:

  • 代品として動作する
  • 特定のパターンを試すために指定された挙動をする

テストスパイ

「テストスパイ」(あるいは単に「スパイ」)とは、 SUT が利用するコンポーネントにどのようなアクセスがあったかを記録するものです。

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

テスト対象の一連の処理を実行した後に「メソッドが呼び出されたかどうか」「メソッドが呼び出された回数」「メソッドに渡された実引数」等を検証するためにテストスパイを使用することができます。 『 xUnit Test Patterns 』の用語でいうなら、 SUT から外部への「間接アウトプット」(= indirect outputs )を確認するためのものと言えます。

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

ポイント:

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

モックオブジェクト

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

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

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

ポイント:

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

フェイクオブジェクト

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

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

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

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

ポイント:

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

(ダミーオブジェクト)

「ダミーオブジェクト」とは、テスト中での SUT の利用に必要なコンポーネントの代品です。

ただし、上で挙げた他のものとは異なり何の機能も備えていません。

ダミーオブジェクトを使いたくなる代表的なケースは、メソッドの呼び出しやインスタンスの生成の際の引数の型チェックをパスする必要があるが、型チェックをパスするためだけにオブジェクトを用意したくない(または用意するのが面倒な)場合です。

『 xUnit Test Patterns 』の中では、厳密には SUT が利用するわけではないという意味で、「ダミーオブジェクトはテストダブルではない」といった説明がなされています。 ここで「(ダミーオブジェクト)」と丸かっこ付きにしているのはそのためです。

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

ポイント:

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

(ここからは「テストスタブ」等の長い名称を使わずに短い「スタブ」等の名称を使います)

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

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

  • スタブとは … 本物のコンポーネントの代わりに事前に指定された挙動をする
  • スパイとは … コンポーネントへのアクセスを記録する(記録されたデータは事後のチェックに使える)
  • モックとは … コンポーネントへのアクセスを処理中にチェックする

スパイとモックはスタブの機能を兼ね備えていることがあるので(私が使うテストダブルライブラリでは兼ね備えているケースの方が多い気がします)、その場合は次のとおりになります。

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

スパイとモックの違いについては、スパイが事後の検証機能を備えている場合は、検証機能を持っている点はどちらも共通で、検証のタイミングだけが異なります。 スパイは SUT の処理をひととおり実行した後に検証を行うのに対し、モックは SUT の処理の最中に検証を行います。 実際のテストコードの見た目としては、スパイは事後に検証を行い、モックは事前に期待値をセットする形で検証を行っているように見えます。

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

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

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

参考