web-dev-qa-db-ja.com

SpockテストフレームワークでのMock / Stub / Spyの違い

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

80
Q Liu

注意:今後のパラグラフでは、単純化しすぎ、おそらく少しでも偽造します。詳細については、 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?")
  }
}
82
kriegaex

質問はSpockフレームワークのコンテキスト内にあり、現在の回答ではこれを考慮に入れていないと思います。

Spock docs に基づく(カスタマイズされた例、独自の文言が追加された):

スタブ:特定の方法でコラボレーターがメソッド呼び出しに応答するために使用されます。メソッドをスタブ化するとき、メソッドが呼び出されるかどうか、および何回呼び出されるかは気にしません。呼び出されるたびに、何らかの値を返すか、何らかの副作用を実行するだけです。

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()

Spy:は常に、実際のことを行う独自のメソッドを持つ実際のオブジェクトに基づいています。スタブのように使用して、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()の使用を避けてください。そうすることは臭いであり、テスト中のオブジェクトの誤ったテストまたは誤ったデザインを示唆する可能性があります。

41
mikhail

簡単な言葉で:

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

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

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

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

11
GKS