1. ホーム
  2. testing

[解決済み] SpockテストフレームワークにおけるMock/Stub/Spyの相違点

2022-05-14 01:49:25

質問

SpockテストにおけるMock、Stub、Spyの違いがわからず、ネットで見ているチュートリアルでも詳しく説明されていません。

どのように解決するのですか?

注意してください。私はこれからの段落で、簡略化しすぎたり、もしかしたら少し誤魔化したりするつもりです。より詳細な情報については Martin Fowlerのウェブサイト .

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

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

つまり、基本的には実際のオブジェクトにスタブメソッドの影がついたものです(すべてではありません)。スタブでないメソッドは、元のオブジェクトにルーティングされるだけです。このように、quot;cheap" やつまらないメソッドには本来の動作を、quot;expensive" や複雑なメソッドには偽の動作を持たせることができるのです。


2017-02-06に更新しました。 実際、ユーザーmikhailの回答は、上記の私のオリジナルのものよりもSpockに特化したものです。ですから、Spockの範囲内では、彼が説明することは正しいのですが、それは私の一般的な答えを偽るものではありません。

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

ここで、何が可能で何が不可能かを示す、実行可能なテスト例を示します。mikhail のスニペットよりも少し有益なものです。私自身の答えを改善するよう促してくれた彼に感謝します! :-)

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?")
  }
}