Differenza tra Mock / Stub / Spy nel framework di test Spock


Risposte:


94

Attenzione: ho intenzione di semplificare eccessivamente e forse anche di falsificare leggermente nei prossimi paragrafi. Per informazioni più dettagliate vedere il sito Web di Martin Fowler .

Un mock è una classe fittizia che sostituisce una classe reale, restituendo qualcosa come null o 0 per ogni chiamata al metodo. Si utilizza un mock se è necessaria un'istanza fittizia di una classe complessa che altrimenti utilizzerebbe risorse esterne come connessioni di rete, file o database o magari userebbe dozzine di altri oggetti. Il vantaggio dei mock è che puoi isolare la classe sottoposta a test dal resto del sistema.

Uno stub è anche una classe fittizia che fornisce alcuni risultati più specifici, preparati o preregistrati e riprodotti a determinate richieste sotto test. Si potrebbe dire che uno stub è una finta fantasia. In Spock leggerai spesso dei metodi stub.

Una spia è una specie di ibrido tra oggetto reale e stub, ovvero è fondamentalmente l'oggetto reale con alcuni (non tutti) metodi oscurati dai metodi stub. I metodi non stubbed vengono semplicemente instradati all'oggetto originale. In questo modo puoi avere un comportamento originale per metodi "economici" o banali e un comportamento falso per metodi "costosi" o complessi.


Aggiornamento 2017-02-06: In realtà la risposta dell'utente mikhail è più specifica per Spock rispetto alla mia originale sopra. Quindi, nell'ambito di Spock, ciò che descrive è corretto, ma ciò non falsifica la mia risposta generale:

  • Uno stub si occupa di simulare un comportamento specifico. In Spock questo è tutto ciò che uno stub può fare, quindi è la cosa più semplice.
  • Un mock si occupa di sostituire un oggetto reale (possibilmente costoso), fornendo risposte non operative per tutte le chiamate ai metodi. A questo proposito, una simulazione è più semplice di uno stub. Ma in Spock, un mock può anche stubare i risultati del metodo, cioè essere sia un mock che uno stub. Inoltre, in Spock possiamo contare quante volte durante un test sono stati chiamati metodi mock specifici con determinati parametri.
  • Una spia avvolge sempre un oggetto reale e di default instrada tutte le chiamate di metodo all'oggetto originale, passando anche attraverso i risultati originali. Il conteggio delle chiamate al metodo funziona anche per le spie. In Spock, una spia può anche modificare il comportamento dell'oggetto originale, manipolando i parametri della chiamata al metodo e / oi risultati o bloccando del tutto la chiamata ai metodi originali.

Ora ecco un esempio di test eseguibile, che dimostra cosa è possibile e cosa no. È un po 'più istruttivo degli snippet di Mikhail. Molte grazie a lui per avermi ispirato a migliorare la mia risposta! :-)

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

La differenza tra mock e stub non è chiara qui. Con i mock, si vuole verificare il comportamento (se e quante volte il metodo verrà chiamato). Con gli stub, si verifica solo lo stato (ad esempio la dimensione della raccolta dopo il test). Cordiali saluti: anche i mock possono fornire risultati preparati.
— chipiik

Grazie @mikhail e chipiik per il tuo feedback. Ho aggiornato la mia risposta, sperando di migliorare e chiarire alcune cose che avevo scritto originariamente. Dichiarazione di non responsabilità: nella mia risposta originale ho detto che stavo semplificando eccessivamente e falsificando leggermente alcuni fatti relativi a Spock. Volevo che le persone capissero le differenze fondamentali tra lo stubbing, lo scherno e lo spionaggio.
— kriegaex

@chipiik, ancora una cosa come risposta al tuo commento: ho istruito i team di sviluppo per molti anni e li ho visti usare Spock o altre JUnit con altri framework fittizi. Nella maggior parte dei casi, quando si usano i mock, non lo hanno fatto per verificare il comportamento (cioè contare le chiamate ai metodi) ma per isolare il soggetto sotto test dal suo ambiente. Il conteggio delle interazioni IMO è solo un accessorio aggiuntivo e dovrebbe essere usato con attenzione e con parsimonia perché c'è la tendenza che tali test si interrompano quando testano il cablaggio dei componenti più del loro comportamento effettivo.
— kriegaex

La sua risposta breve ma comunque molto utile
— Chaklader Asfak Arefe

55

La domanda era nel contesto del framework Spock e non credo che le risposte attuali ne tengano conto.

Basato su documenti di Spock (esempi personalizzati, aggiunta la mia formulazione):

Stub: utilizzato per fare in modo che i collaboratori rispondano alle chiamate di metodo in un certo modo. Quando si esegue lo stubbing di un metodo, non ti interessa se e quante volte il metodo verrà chiamato; vuoi solo che restituisca un valore o che esegua un effetto collaterale, ogni volta che viene chiamato.

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

Mock: utilizzato per descrivere le interazioni tra l'oggetto sotto specifica e i suoi collaboratori.

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

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

Un Mock può agire come un Mock e uno Stub:

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

Spia: si basa sempre su un oggetto reale con metodi originali che fanno cose reali. Può essere utilizzato come uno stub per modificare i valori di ritorno dei metodi di selezione. Può essere usato come un Mock per descrivere le interazioni.

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
}

Sommario:

  • Uno Stub () è uno Stub.
  • Un Mock () è uno Stub e Mock.
  • Una spia () è uno stub, una simulazione e una spia.

Evita di usare Mock () se Stub () è sufficiente.

Evita di usare Spy () se puoi, doverlo fare potrebbe essere un odore e accenni a test errati o design errato dell'oggetto sotto test.


1
Solo per aggiungere: un altro motivo per cui vuoi ridurre al minimo l'uso di mock, è che un mock è molto simile a un assert, in quanto controlli le cose su un mock che potrebbe non superare il test e vuoi sempre ridurre al minimo la quantità di assegni fai in un test, per mantenerlo concentrato e semplice. Quindi idealmente dovrebbe esserci un solo mock per test.
— Sammi

1
"Una spia () è uno stub, una simulazione e una spia." questo non è vero per le spie Sinon?
— K - La tossicità in SO sta crescendo.

2
Ho appena dato una rapida occhiata alle spie Sinon e sembra che non si comportino come Mock o Stub. Nota che questa domanda / risposta è nel contesto di Spock, che è Groovy, non JS.
— mikhail

Questa dovrebbe essere la risposta corretta, poiché è limitata al contesto di Spock. Inoltre, dire che uno stub è una finta fantasia potrebbe essere fuorviante, poiché una finta ha funzionalità extra (controllo del conteggio delle chiamate) che lo stub non ha (finto> più elaborato dello stub). Di nuovo, beffa e muta come per Spock.
— CGK

13

In parole povere:

Mock: prendi in giro un tipo e al volo ottieni un oggetto creato. I metodi in questo oggetto fittizio restituiscono i valori predefiniti del tipo restituito.

Stub: crei una classe stub in cui i metodi vengono ridefiniti con la definizione secondo le tue esigenze. Es: nel metodo dell'oggetto reale si chiama un'API esterna e si restituisce il nome utente contro e id. Nel metodo dell'oggetto stubbed si restituisce un nome fittizio.

Spia: crei un oggetto reale e poi lo spii. Ora puoi deridere alcuni metodi e scegliere di non farlo per alcuni.

Una differenza di utilizzo è che non puoi simulare oggetti a livello di metodo. mentre è possibile creare un oggetto predefinito in metodo e quindi spiarlo per ottenere il comportamento desiderato dei metodi nell'oggetto spiato.


0

Gli stub servono solo per facilitare lo unit test, non fanno parte del test. I mock, fanno parte del test, parte della verifica, parte del pass / fail.

Quindi, supponiamo di avere un metodo che accetta un oggetto come parametro. Non fai mai nulla che modifichi questo parametro nel test. Leggi semplicemente un valore da esso. Questo è uno stub.

Se modifichi qualcosa o devi verificare una sorta di interazione con l'oggetto, è una finta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.