it-swarm.com.de

Unterschied zwischen Mock / Stub / Spy im Spock-Test-Framework

Ich verstehe den Unterschied zwischen Mock, Stub und Spy beim Spock-Testen nicht und die Tutorials, die ich online angeschaut habe, erklären sie nicht im Detail.

80
Q Liu

Achtung: Ich werde in den nächsten Abschnitten zu stark vereinfachen und vielleicht sogar leicht verfälschen. Weitere Informationen finden Sie unter Martin Fowlers Website .

Ein Mock ist eine Dummy-Klasse, die eine echte Klasse ersetzt und für jeden Methodenaufruf so etwas wie null oder 0 zurückgibt. Sie verwenden einen Mock, wenn Sie eine Dummy-Instanz einer komplexen Klasse benötigen, die ansonsten externe Ressourcen wie Netzwerkverbindungen, Dateien oder Datenbanken oder möglicherweise Dutzende anderer Objekte verwenden würde. Der Vorteil von Mocks ist, dass Sie die zu testende Klasse vom Rest des Systems isolieren können.

Ein Stub ist auch eine Dummy-Klasse, die einige spezifischere, vorbereitete oder zuvor aufgezeichnete, wiedergegebene Ergebnisse für bestimmte getestete Anforderungen bereitstellt. Man könnte sagen, ein Stummel ist ein ausgefallener Spott. In Spock lesen Sie oft über Stub-Methoden.

Ein Spion ist eine Art Hybrid zwischen realem Objekt und Stub, d. H. Es ist im Grunde das reale Objekt mit einigen (nicht allen) Methoden, die von Stub-Methoden beschattet werden. Nicht gestubte Methoden werden nur zum ursprünglichen Objekt weitergeleitet. Auf diese Weise können Sie ein ursprüngliches Verhalten für "billige" oder triviale Methoden und ein falsches Verhalten für "teure" oder komplexe Methoden erzielen.


Update 2017-02-06: Tatsächlich ist die Antwort von Benutzer mikhail spezifischer für Spock als meine ursprüngliche Antwort oben. Im Rahmen von Spock ist das, was er beschreibt, richtig, aber das fälscht meine allgemeine Antwort nicht:

  • Ein Stub befasst sich mit der Simulation eines bestimmten Verhaltens. In Spock ist das alles, was ein Stub tun kann, also ist es die einfachste Sache.
  • Ein Mock befasst sich damit, für ein (möglicherweise teures) reales Objekt einzustehen und für alle Methodenaufrufe keine Antwort zu geben. In dieser Hinsicht ist ein Mock einfacher als ein Stub. Bei Spock kann sich jedoch auch eine Scheinmethode ergeben, d. H. Sowohl Schein als auch Stich. Außerdem können wir in Spock zählen, wie oft bestimmte Mock-Methoden mit bestimmten Parametern während eines Tests aufgerufen wurden.
  • Ein Spion umschließt immer ein reales Objekt und leitet standardmäßig alle Methodenaufrufe an das ursprüngliche Objekt weiter, wobei auch die ursprünglichen Ergebnisse durchlaufen werden. Das Zählen von Methodenaufrufen funktioniert auch für Spione. In Spock kann ein Spion auch das Verhalten des ursprünglichen Objekts ändern, indem er Methodenaufrufparameter und/oder -ergebnisse manipuliert oder das Aufrufen der ursprünglichen Methoden blockiert.

Hier ist ein ausführbarer Beispieltest, der zeigt, was möglich ist und was nicht. Es ist ein bisschen lehrreicher als Mikhails Schnipsel. Vielen Dank an ihn, dass er mich inspiriert hat, meine eigene Antwort zu verbessern! :-)

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

Die Frage befand sich im Kontext des Spock-Frameworks und ich glaube nicht, dass die aktuellen Antworten dies berücksichtigen.

Basierend auf Spock docs (Beispiele angepasst, eigene Formulierung hinzugefügt):

Stub: Wird verwendet, um Kollaborateure auf bestimmte Weise auf Methodenaufrufe zu reagieren. Beim Stoppen einer Methode ist es egal, ob und wie oft die Methode aufgerufen wird. Sie möchten nur, dass es einen Wert zurückgibt oder einen Nebeneffekt erzeugt, wenn es aufgerufen wird.

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

Mock: Wird zur Beschreibung der Interaktionen zwischen dem spezifizierten Objekt und seinen Mitarbeitern verwendet.

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

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

Ein Mock kann als Mock und Stub fungieren:

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

Spy: basiert immer auf einem realen Objekt mit originellen Methoden, die reale Dinge tun. Kann wie ein Stub verwendet werden, um die Rückgabewerte ausgewählter Methoden zu ändern. Kann wie ein Mock verwendet werden, um Interaktionen zu beschreiben.

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
}

Zusammenfassung:

  • Ein Stub () ist ein Stub.
  • Ein Mock () ist ein Stub and Mock.
  • Ein Spy () ist ein Stub, Mock and Spy.

Vermeiden Sie die Verwendung von Mock (), wenn Stub () ausreicht.

Vermeiden Sie es, Spy () zu verwenden, wenn Sie können. Dies könnte ein Geruch und ein Hinweis auf einen falschen Test oder ein falsches Design des zu testenden Objekts sein.

41
mikhail

In einfachen Worten:

Mock: Sie verspotten einen Typ und erhalten sofort ein Objekt erstellt. Methoden in diesem Mock-Objekt geben die Standardwerte des Rückgabetyps zurück.

Stub: Sie erstellen eine Stub-Klasse, in der Methoden mit einer Definition gemäß Ihren Anforderungen neu definiert werden. Beispiel: In der Real Object-Methode rufen Sie und external api auf und geben den Benutzernamen gegen und id zurück. In der Stubbed Object-Methode geben Sie einen Dummy-Namen zurück.

Spion: Sie erstellen ein reales Objekt und dann spionieren Sie es aus. Jetzt können Sie einige Methoden verspotten und sich dafür entscheiden, dies für einige nicht zu tun.

Ein Verwendungsunterschied ist Sie können keine Objekte auf Methodenebene verspotten. Sie können in method ein Standardobjekt erstellen und es dann ausspionieren, um das gewünschte Verhalten von Methoden in spionierten Objekten zu erhalten.

11
GKS