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

101

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

Wang-Zhao-Liu QM
quelle

Antworten:

94

Achtung: Ich werde in den kommenden Absätzen zu stark vereinfachen und vielleicht sogar leicht verfälschen. Weitere Informationen finden Sie auf der Website von Martin Fowler .

Ein Mock ist eine Dummy-Klasse, die eine echte ersetzt und für jeden Methodenaufruf so etwas wie null oder 0 zurückgibt. Sie verwenden ein Modell, wenn Sie eine Dummy-Instanz einer komplexen Klasse benötigen, die andernfalls externe Ressourcen wie Netzwerkverbindungen, Dateien oder Datenbanken oder möglicherweise Dutzende anderer Objekte verwenden würde. Der Vorteil von Mocks besteht darin, 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 aufgezeichnete, wiedergegebene Ergebnisse für bestimmte zu testende Anforderungen liefert. Man könnte sagen, ein Stummel ist ein schickes Mock. In Spock werden Sie häufig über Stub-Methoden lesen.

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


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 verfälscht meine allgemeine Antwort nicht:

  • Ein Stub befasst sich mit der Simulation eines bestimmten Verhaltens. In Spock ist dies alles, was ein Stub tun kann, also ist es die einfachste Sache.
  • Bei einem Mock geht es darum, für ein (möglicherweise teures) reales Objekt einzutreten und für alle Methodenaufrufe No-Op-Antworten bereitzustellen. In dieser Hinsicht ist ein Mock einfacher als ein Stub. In Spock kann ein Mock aber auch zu Stub-Methoden führen, dh sowohl zu einem Mock als auch zu einem Stub. Darüber hinaus können wir in Spock zählen, wie oft bestimmte Scheinmethoden 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. Die Methodenaufrufzählung 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 verhindert, dass die ursprünglichen Methoden überhaupt aufgerufen werden.

Hier ist ein ausführbarer Beispieltest, der zeigt, was möglich ist und was nicht. Es ist etwas 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?")
  }
}
Kriegaex
quelle
Der Unterschied zwischen Mock und Stub ist hier nicht klar. Mit Mocks möchte man das Verhalten überprüfen (ob und wie oft die Methode aufgerufen wird). Bei Stubs wird nur der Status überprüft (z. B. Größe der Sammlung nach dem Test). Zu Ihrer Information: Mocks können auch vorbereitete Ergebnisse liefern.
Chipiik
Vielen Dank an @mikhail und chipiik für Ihr Feedback. Ich habe meine Antwort aktualisiert und hoffentlich einige Dinge verbessert und geklärt, die ich ursprünglich geschrieben habe. Haftungsausschluss: In meiner ursprünglichen Antwort habe ich gesagt, dass ich einige Spock-bezogene Fakten zu stark vereinfacht und leicht verfälscht habe. Ich wollte, dass die Leute die grundlegenden Unterschiede zwischen Stubbing, Spott und Spionage verstehen.
Kriegaex
@chipiik, noch eine Antwort auf Ihren Kommentar: Ich trainiere seit vielen Jahren Entwicklungsteams und habe gesehen, dass sie Spock oder andere JUnit mit anderen Mock-Frameworks verwenden. In den meisten Fällen haben sie Mocks nicht verwendet, um das Verhalten zu überprüfen (dh Methodenaufrufe zu zählen), sondern um das zu testende Subjekt von seiner Umgebung zu isolieren. Interaktionszählung IMO ist nur ein Add-On-Goodie und sollte nachdenklich und sparsam eingesetzt werden, da solche Tests dazu neigen, zu brechen, wenn sie die Verkabelung von Komponenten mehr als ihr tatsächliches Verhalten testen.
Kriegaex
Seine kurze, aber immer noch sehr hilfreiche Antwort
Chaklader Asfak Arefe
55

Die Frage stand im Zusammenhang mit dem Spock-Framework und ich glaube nicht, dass die aktuellen Antworten dies berücksichtigen.

Basierend auf Spock-Dokumenten (Beispiele angepasst, mein eigener Wortlaut hinzugefügt):

Stub: Wird verwendet, um Mitarbeiter dazu zu bringen, auf Methodenaufrufe auf eine bestimmte Weise zu reagieren. Wenn Sie eine Methode stubben, ist es Ihnen egal, ob und wie oft die Methode aufgerufen wird. Sie möchten nur, dass es einen Wert zurückgibt oder einen Nebeneffekt ausführt, wenn es aufgerufen wird.

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

Mock: Wird verwendet, um Interaktionen zwischen dem zu spezifizierenden Objekt und seinen Mitarbeitern zu beschreiben.

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

Spion: 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 Spion () ist ein Stub, Mock und Spy.

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

Vermeiden Sie die Verwendung von Spy (), wenn Sie können. Dies kann ein Geruch sein und Hinweise auf einen falschen Test oder ein falsches Design des zu testenden Objekts geben.

mikhail
quelle
1
Nur um hinzuzufügen: Ein weiterer Grund, warum Sie die Verwendung von Mocks minimieren möchten, ist, dass ein Mock einer Behauptung sehr ähnlich ist, indem Sie Dinge an einem Mock überprüfen, die den Test möglicherweise nicht bestehen, und Sie möchten immer die Anzahl der Überprüfungen minimieren Sie tun dies in einem Test, um den Test fokussiert und einfach zu halten. Idealerweise sollte es also nur einen Schein pro Test geben.
Sammi
1
"Ein Spion () ist ein Stummel, ein Spott und ein Spion." das gilt nicht für sinon spione?
K - Die Toxizität in SO nimmt zu.
2
Ich habe mir nur kurz Sinon-Spione angesehen und sie sehen so aus, als würden sie sich nicht wie Mocks oder Stubs verhalten. Beachten Sie, dass diese Frage / Antwort im Kontext von Spock steht, bei dem es sich um Groovy handelt, nicht um JS.
Mikhail
Dies sollte die richtige Antwort sein, da dies auf den Spock-Kontext beschränkt ist. Es kann auch irreführend sein, zu sagen, dass ein Stub ein ausgefallener Mock ist, da ein Mock über zusätzliche Funktionen verfügt (Überprüfung der Aufrufanzahl), die der Stub nicht bietet (Mock> Fancier als Stub). Wieder Mock und Stubs nach Spock.
CGK
13

In einfachen Worten:

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

Stub: Sie erstellen eine Stub-Klasse, in der Methoden gemäß Ihren Anforderungen mit der Definition neu definiert werden. Beispiel: In der Real-Object-Methode rufen Sie eine externe 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 echtes Objekt und spionieren es dann aus. Jetzt können Sie einige Methoden verspotten und dies für einige nicht tun.

Ein Verwendungsunterschied besteht darin, dass Sie keine Objekte auf Methodenebene verspotten können. Sie können ein Standardobjekt in der Methode erstellen und es dann ausspionieren, um das gewünschte Verhalten der Methoden im ausspionierten Objekt zu erhalten.

GKS
quelle
0

Stubs dienen eigentlich nur zur Erleichterung des Unit-Tests, sie sind nicht Teil des Tests. Mocks sind Teil des Tests, Teil der Überprüfung, Teil des Bestehens / Nichtbestehens.

Angenommen, Sie haben eine Methode, die ein Objekt als Parameter verwendet. Sie tun niemals etwas, was diesen Parameter im Test ändert. Sie lesen einfach einen Wert daraus. Das ist ein Stummel.

Wenn Sie etwas ändern oder eine Interaktion mit dem Objekt überprüfen müssen, handelt es sich um eine Verspottung.

Mehr als fünf
quelle