Mock と Stub と Spy を調べてみた

Spock 0.7 から、これまでの Mock に加えて
Stub と Spy も使えるようになったので今回調べてみました。

ぶっちゃけ Mock だけ使えれば事足りるんじゃないかと思ってました。
(調べてみた後も若干そう思ってます)

テストダブル(Test Double)

テストダブルは、テストする対象が依存しているオブジェクトを置き換える代役です。
モック、スタブ、スパイはテストダブルになります。

テスト関連本だと、当然のように「ダブル」って言葉が出てきますが、
ちゃんと理解していないと混乱する用語だと思います。

テストダブルについては『JUnit 実践入門』の第11章に詳しく書かれています。
私はこれを読んで、理解ができました。


Spock のテストダブル

Interaction Based Testing — Spock 1.0-SNAPSHOT

Mock を使った置き換え手法は以前からあって参考となるサイトもたくさんあります。
この Mock に加えて Spock 0.7 からは次のようなテストダブルが使えるようになっています。

・Stub
・Spy
・GroovyMock
・GroovySpy

GroovyMock と GroovySpy は置いておいて(英語読むのが面倒なので…)
今回は Stub と Spy を。

ここからは Spock に関する検証です。
他のテストフレームワークでは異なる動作になるかもしれません。あしからず。

ソースコードのサンプル

まずはテスト対象となるソースコードです。

class Parent {

    Child child

    Parent(Child child) {
        this.child = child
    }

    int add(int a, int b) {
        child.add(a, b)
    }
}

class Child {

    int add(int a, int b) {
        a + b
    }
}

Parent クラスがテスト対象です。
Child クラスがテスト対象ではないですが、Parent クラスが依存しています。
この Child クラスをテストダブルに置き換えて Parent クラスのテストをしやすくします。

テストコードのサンプル

Mock、Stub、Spy をそれぞれ使ったテストコードです。

class ParentTest extends Specification {
    Parent sut

    def "Mock を使ってみる"() {
        given:
        Child child = Mock()
        1 * child.add(1, 2) >> 10
        1 * child.add(2, 3)
        child.add(3, 4)

        when:
        sut = new Parent(child)

        then:
        sut.add(1, 2) == 10 // ふるまいが置き換えられる
        sut.add(2, 3) == 0 // 5 じゃない。なにもしない振る舞いになる
        sut.add(3, 4) == 0 // 7 じゃない。なにもしない振る舞いになる
        sut.add(4, 5) == 0 // 9 じゃない。なにもしない振る舞いになる
    }

    def "Stub を使ってみる"() {
        given:
        Child child = Stub()
        child.add(1, 2) >> 10 // カーディナリティ(1 * 〜)はエラーになる

        when:
        sut = new Parent(child)

        then:
        sut.add(1, 2) == 10 // ふるまいが置き換えられる
        sut.add(2, 3) == 0 // 5 じゃない。なにもしない振る舞いになる
    }

    def "Spy を使ってみる"() {
        given:
        Child child = Spy()
        1 * child.add(1, 2) >> 10
        1 * child.add(2, 3)

        when:
        sut = new Parent(child)

        then:
        sut.add(1, 2) == 10 // ふるまいが置き換えられる + カーディナリティの検証
        sut.add(2, 3) == 5 // 実際に呼ばれる + カーディナリティの検証
        sut.add(3, 4) == 7 // 実際によばれる
    }
}

Mock は Stub を包含している

Mock も Stub もふるまいを明示的に置き換えれば、そのようにふるまいます。
置き換えていないふるまいは、なにもしないように&なにも返さないようになります。

カーディナリティを使えるかどうかだけが Mock と Stub の違いのようです。
だったら、Mock だけ使えばいいはず…

モックとスタブを明確に区別したほうがテストコードの可読性が上がるというのであれば
Stub を使うでしょうが、私は Mock でカーディナリティを使う/使わないで十分だと思います。

Spy は使い方がむずかしいかも

Spy 本来の目的は呼び出しの監視、つまりオブジェクトが何回呼ばれたかを検証することです。
しかし、スタブのようにふるまいを置き換えることもできるようです。
ふるまいを置き換えなかったら、実際にそのメソッドが呼び出されます。

Mock と Spy の違いは明示的にふるまいを置き換えなかったメソッドを
・なにもしない、なにも返さないふるまいに置き換える = Mock
・本来のふるまいを行う(実際に呼び出される) = Spy
の違いのようです。

Spy を使用したとき、ふつうは依存するオブジェクトのふるまいが実際に行われます。
テスト対象のソースコードのみをテストしたいというのであれば、呼び出し先のふるまいに依存するため、脆いテストになりやすいです。

しかし、Spy をうまく使えば結合レベルのテストも実施できます。
なんでもかんでも Mock を使うと、あまりにもオブジェクト間の依存性を排除したテストになります。
DBアクセスといいたテストの事前準備が大変なパターンではない場合では、Spy を使って実際に結合して実行してみるのもありかと思います。

実際に動かしてみて

テストダブルについて理解があいまいでしたが、自分なりに整理ができました。