Diferencia entre Mock / Stub / Spy en el marco de prueba de Spock


No entiendo la diferencia entre Mock, Stub y Spy en las pruebas de Spock y los tutoriales que he estado mirando en línea no los explican en detalle.

Author: kriegaex, 2014-06-25

3 answers

Atención: Voy a simplificar demasiado y tal vez incluso falsificar ligeramente en los próximos párrafos. Para obtener información más detallada, consulte El sitio web de Martin Fowler .

Un mock es una clase ficticia que reemplaza a una real, devolviendo algo como null o 0 para cada llamada al método. Usted usa un mock si necesita una instancia ficticia de una clase compleja que de otra manera usaría recursos externos como conexiones de red, archivos o bases de datos o tal vez usaría docenas de otros objetos. La ventaja de se burla es que puede aislar la clase bajo prueba del resto del sistema.

Un stub es también una clase ficticia que proporciona algunos resultados más específicos, preparados o pregrabados, reproducidos a ciertas solicitudes bajo prueba. Se podría decir que un trozo es una burla elegante. En Spock a menudo leerá acerca de los métodos de talón.

Un espía es una especie de híbrido entre objeto real y stub, es decir, es básicamente el objeto real con algunos (no todos) métodos sombreados por métodos stub. Los métodos no stubbed son solo enrutado al objeto original. De esta manera puede tener un comportamiento original para métodos "baratos" o triviales y un comportamiento falso para métodos "caros" o complejos.


Actualización 2017-02-06: En realidad la respuesta del usuario mikhail es más específica para Spock que mi respuesta original anterior. Así que dentro del alcance de Spock, lo que él describe es correcto, pero eso no falsifica mi respuesta general:

  • Un stub se refiere a la simulación de un comportamiento específico. En Spock esto es todo un trozo puede hacer, por lo que es una especie de la cosa más simple.
  • Un mock se ocupa de sustituir un objeto real (posiblemente caro), proporcionando respuestas no-op para todas las llamadas a métodos. En este sentido, un simulacro es más simple que un trozo. Pero en Spock, un simulacro también puede ser resultado del método stub, es decir, ser tanto un simulacro como un stub. Además, en Spock podemos contar con qué frecuencia se han llamado métodos simulados específicos con ciertos parámetros durante una prueba.
  • Un espía siempre envuelve un objeto real y el valor predeterminado enruta todas las llamadas a los métodos al objeto original, pasando también por los resultados originales. Método de conteo de llamadas también funciona para los espías. En Spock, un espía también puede modificar el comportamiento del objeto original, manipulando parámetros de llamada al método y / o resultados o bloqueando la llamada a los métodos originales.

Ahora aquí hay una prueba de ejemplo ejecutable, que demuestra lo que es posible y lo que no lo es. Es un poco más instructivo que los fragmentos de Mikhail. Muchas gracias a él por inspirarme a mejorar mi propia respuesta! :-)

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?")
  }
}
 68
Author: kriegaex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-22 11:27:44

La pregunta estaba en el contexto del marco Spock y no creo que las respuestas actuales tengan esto en cuenta.

Basado en documentos de Spock (ejemplos personalizados, mi propia redacción añadida):

Talón: Se utiliza para hacer que los colaboradores respondan a las llamadas a los métodos de una determinada manera. Al stubbing un método, no te importa si y cuántas veces se va a llamar al método; solo quieres que devuelva algún valor, o realice algún efecto secundario, cada vez que se called.

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

Mock: Se utiliza para describir las interacciones entre el objeto bajo especificación y sus colaboradores.

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

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

Un Simulacro puede actuar como un Simulacro y un Trozo:

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

Espía: Siempre se basa en un objeto real con métodos originales que hacen cosas reales. Se puede usar como un Stub para cambiar los valores de retorno de métodos selectos. Puede ser utilizado como una burla para describir interacción.

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
}

Resumen:

  • Un Stub() es un Stub.
  • Una Burla() es un Trozo y Burla.
  • Un Espía() es un Trozo, Burla y Espía.

Evite usar Mock() si Stub() es suficiente.

Evite usar Spy() si puede, tener que hacerlo podría ser un olor y insinuar una prueba incorrecta o un diseño incorrecto del objeto bajo prueba.

 29
Author: mikhail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-05 20:21:59

En términos simples:

Mock: Se burla de un tipo y sobre la marcha se crea un objeto. Methods en este objeto mock devuelve los valores predeterminados del tipo return.

Stub: Crea una clase stub donde los métodos se redefinen con definición según su requisito. Ej: En el método de objeto real se llama a una api externa y se devuelve el nombre de usuario y el id. En stubbed object method devuelve algún nombre ficticio.

Espía: Creas un objeto real y luego lo espías. Ahora puede burlarse de algunos métodos y eligió no hacerlo para algunos.

Una diferencia de uso es no puede simular objetos de nivel de método. mientras que puede crear un objeto predeterminado en method y luego espiarlo para obtener el comportamiento deseado de los métodos en spied object.

 11
Author: GKS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-02 10:56:32