Spockテストフレームワークのモック/スタブ/スパイの違い


101

SpockテストでのMock、Stub、Spyの違いがわかりません。オンラインで見ているチュートリアルでは、それらについて詳しく説明していません。

回答:


94

注意:次の段落では、単純化しすぎて、おそらく少し改ざんするつもりです。詳細については、Martin FowlerのWebサイトを参照してください。

モックは、実際のクラスを置き換えるダミークラスであり、メソッド呼び出しごとにnullまたは0のようなものを返します。複雑なクラスのダミーインスタンスが必要な場合は、モックを使用します。そうしないと、ネットワーク接続、ファイル、データベースなどの外部リソースを使用したり、他のオブジェクトを何十も使用したりする可能性があります。モックの利点は、テスト中のクラスをシステムの他の部分から分離できることです。

スタブは、テスト中の特定のリクエストに対して、より具体的な、準備された、または事前に記録された、再生された結果を提供するダミークラスでもあります。スタブは派手なモックと言えます。Spockでは、スタブメソッドについてよく読みます。

スパイは、実際のオブジェクトとスタブのハイブリッドのようなものです。つまり、基本的には、スタブメソッドによって隠されたいくつかの(すべてではない)メソッドを持つ実際のオブジェクトです。スタブ化されていないメソッドは、元のオブジェクトにルーティングされるだけです。このようにして、「安価な」メソッドまたは自明なメソッドの元の動作と、「高価な」メソッドまたは複雑なメソッドの偽の動作を持たせることができます。


2017-02-06の更新:実際にユーザーmikhailの答えは、上記の私の元の答えよりもSpockに固有です。したがって、Spockの範囲内では、彼の説明は正しいですが、それは私の一般的な答えを偽ることはありません。

  • スタブは特定の動作のシミュレーションに関係しています。Spockでは、これはすべてスタブで実行できるので、一種の最も単純なものです。
  • モックは、(おそらく高価な)実オブジェクトの代用に関係し、すべてのメソッド呼び出しに対して何もしない応答を提供します。この点で、モックはスタブよりも単純です。ただし、Spockでは、モックはメソッドの結果をスタブすることもできます。つまり、モックとスタブの両方になります。さらに、Spockでは、特定のパラメーターを持つ特定のモックメソッドがテスト中に呼び出された頻度をカウントできます。
  • スパイは常に実際のオブジェクトをラップし、デフォルトではすべてのメソッド呼び出しを元のオブジェクトにルーティングし、元の結果も通過させます。メソッド呼び出しカウントは、スパイに対しても機能します。Spockでは、スパイは元のオブジェクトの動作を変更して、メソッド呼び出しのパラメータや結果を操作したり、元のメソッドが呼び出されないようにしたりすることもできます。

次に、実行可能なテストの例を示し、何が可能で何が不可能かを示します。ミハイルのスニペットよりも少し参考になります。私自身の答えを改善するように私を鼓舞してくれた彼に感謝します!:-)

package de.scrum_master.stackoverflow

import org.spockframework.mock.TooFewInvocationsError
import org.spockframework.runtime.InvalidSpecException
import spock.lang.FailsWith
import spock.lang.Specification

class MockStubSpyTest extends Specification {

  static class Publisher {
    List<Subscriber> subscribers = new ArrayList<>()

    void addSubscriber(Subscriber subscriber) {
      subscribers.add(subscriber)
    }

    void send(String message) {
      for (Subscriber subscriber : subscribers)
        subscriber.receive(message);
    }
  }

  static interface Subscriber {
    String receive(String message)
  }

  static class MySubscriber implements Subscriber {
    @Override
    String receive(String message) {
      if (message ==~ /[A-Za-z ]+/)
        return "ok"
      return "uh-oh"
    }
  }

  Subscriber realSubscriber1 = new MySubscriber()
  Subscriber realSubscriber2 = new MySubscriber()
  Publisher publisher = new Publisher(subscribers: [realSubscriber1, realSubscriber2])

  def "Real objects can be tested normally"() {
    expect:
    realSubscriber1.receive("Hello subscribers") == "ok"
    realSubscriber1.receive("Anyone there?") == "uh-oh"
  }

  @FailsWith(TooFewInvocationsError)
  def "Real objects cannot have interactions"() {
    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * realSubscriber1.receive(_)
  }

  def "Stubs can simulate behaviour"() {
    given:
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >>> ["hey", "ho"]
    }

    expect:
    stubSubscriber.receive("Hello subscribers") == "hey"
    stubSubscriber.receive("Anyone there?") == "ho"
    stubSubscriber.receive("What else?") == "ho"
  }

  @FailsWith(InvalidSpecException)
  def "Stubs cannot have interactions"() {
    given: "stubbed subscriber registered with publisher"
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >> "hey"
    }
    publisher.addSubscriber(stubSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * stubSubscriber.receive(_)
  }

  def "Mocks can simulate behaviour and have interactions"() {
    given:
    def mockSubscriber = Mock(Subscriber) {
      3 * receive(_) >>> ["hey", "ho"]
    }
    publisher.addSubscriber(mockSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("Hello subscribers")
    1 * mockSubscriber.receive("Anyone there?")

    and: "check behaviour exactly 3 times"
    mockSubscriber.receive("foo") == "hey"
    mockSubscriber.receive("bar") == "ho"
    mockSubscriber.receive("zot") == "ho"
  }

  def "Spies can have interactions"() {
    given:
    def spySubscriber = Spy(MySubscriber)
    publisher.addSubscriber(spySubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * spySubscriber.receive("Hello subscribers")
    1 * spySubscriber.receive("Anyone there?")

    and: "check behaviour for real object (a spy is not a mock!)"
    spySubscriber.receive("Hello subscribers") == "ok"
    spySubscriber.receive("Anyone there?") == "uh-oh"
  }

  def "Spies can modify behaviour and have interactions"() {
    given:
    def spyPublisher = Spy(Publisher) {
      send(_) >> { String message -> callRealMethodWithArgs("#" + message) }
    }
    def mockSubscriber = Mock(MySubscriber)
    spyPublisher.addSubscriber(mockSubscriber)

    when:
    spyPublisher.send("Hello subscribers")
    spyPublisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("#Hello subscribers")
    1 * mockSubscriber.receive("#Anyone there?")
  }
}

モックとスタブの違いはここでは明確ではありません。モックを使用して、動作を検証したいとします(メソッドが呼び出される場合とその回数)。スタブを使用すると、状態のみを検証します(例:テスト後のコレクションのサイズ)。参考までに:モックも準備された結果を提供できます。
チピク2017

フィードバックをありがとう@mikhailとchipiik 私は私の答えを更新しました。うまくいけば、私が最初に書いたいくつかのことを改善し、明確にします。免責事項:私の元の回答では、Spockに関連するいくつかの事実を単純化しすぎて少し改ざんしていると私は言いました。私は人々にスタブ、モック、スパイの基本的な違いを理解して欲しかった。
kriegaex 2017

@chipiik、コメントへの回答としてもう1つ:私は長年にわたって開発チームを指導しており、他のモックフレームワークでSpockまたは他のJUnitを使用しているのを見てきました。モックを使用する場合、ほとんどの場合、動作を確認する(つまり、カウントメソッドの呼び出し)ためにモックを使用しませんでしたが、テスト中の対象をその環境から分離しました。インタラクションカウンティングIMOは単なる追加機能であり、実際の動作よりもコンポーネントの配線をテストする場合、このようなテストは中断する傾向があるため、慎重に慎重に使用する必要があります。
kriegaex 2017

その簡潔でありながら非常に役立つ答え
Chaklader Asfak Arefe

55

問題はSpockフレームワークのコンテキストにあり、現在の回答ではこれが考慮されているとは思いません。

Spockのドキュメントに基づいて(カスタマイズされた例、自分の言い回しを追加):

スタブ: 共同編集者がメソッド呼び出しに特定の方法で応答するために使用されます。メソッドをスタブするとき、メソッドが呼び出されるかどうか、また呼び出される回数は気にしません。呼び出されたときに、何らかの値を返すか、何らかの副作用を実行するだけです。

subscriber.receive(_) >> "ok" // subscriber is a Stub()

モック: 仕様に基づくオブジェクトとその共同作業者との間の相互作用を説明するために使用されます。

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("hello") // subscriber is a Mock()
}

モックはモックおよびスタブとして機能できます。

1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()

スパイ: 常に実際のオブジェクトに基づいており、実際のことを行う独自のメソッドを備えています。スタブのように使用して、selectメソッドの戻り値を変更できます。モックのように使用して、相互作用を説明できます。

def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") >> "ok" // subscriber is a Spy(), used as a Mock an Stub
}

def "should send message to subscriber (actually handle 'receive')"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") // subscriber is a Spy(), used as a Mock, uses real 'receive' function
}

概要:

  • Stub()はスタブです。
  • Mock()はスタブとモックです。
  • Spy()は、スタブ、モック、およびスパイです。

Stub()で十分な場合は、Mock()の使用を避けます。

可能であればSpy()の使用を避けてください。そうする必要がある場合は、匂いが発生したり、不適切なテストやテスト対象のオブジェクトの設計が不適切だったりする可能性があります。


1
追加するだけ:モックの使用を最小限にしたいもう1つの理由は、モックがテストに失敗する可能性のあるモック上のものをチェックし、常にチェックの量を最小限にしたいという点で、モックがアサートに非常に似ていることですテストの焦点を合わせてシンプルにするために、テストを行います。したがって、理想的には、テストごとに1つのモックのみが存在する必要があります。
Sammi

1
「Spy()はスタブ、モック、およびスパイです。」これはシノンスパイには当てはまらないのですか?
K-SOの毒性が高まっています。

2
私はシノンのスパイをざっと見ただけで、彼らはモックやスタブとして振舞わないようです。この質問/回答は、JSではなく、Spockのコンテキストにあることに注意してください。
ミハイル、

これはSpockコンテキストを対象としているため、これが正解です。また、モックにはスタブにはない追加機能(呼び出しカウントのチェック)がある(モック>スタブよりも洗練されている)ため、スタブが派手なモックであると言っても誤解を招く可能性があります。繰り返しになりますが、Spockによるモックとスタブ。
CGK 2016年

13

簡単な言葉で:

モック:タイプをモックし、その場でオブジェクトを作成します。このモックオブジェクトのメソッドは、戻り値の型のデフォルト値を返します。

スタブ:要件に応じた定義でメソッドが再定義されるスタブクラスを作成します。例:実際のオブジェクトメソッドで外部APIを呼び出し、IDに対してユーザー名を返します。スタブ化されたオブジェクトメソッドでは、ダミーの名前を返します。

スパイ:1つの実オブジェクトを作成してから、それをスパイします。これで、いくつかのメソッドをモックすることができ、一部のメソッドをモックしないことを選択しました。

使用法の違いの1つは、メソッドレベルのオブジェクトをモックできないことです。一方、メソッドでデフォルトのオブジェクトを作成し、それをスパイして、スパイされたオブジェクトでメソッドの望ましい動作を取得できます。


0

スタブは実際には単体テストを容易にするためだけのものであり、テストの一部ではありません。モックは、テストの一部、検証の一部、合格/不合格の一部です。

したがって、オブジェクトをパラメーターとして受け取るメソッドがあるとします。テストでこのパラメーターを変更することは何もしません。単にそこから値を読み取ります。それはスタブです。

何かを変更したり、オブジェクトとの何らかの相互作用を確認する必要がある場合、それはモックです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.