No entiendo la diferencia entre Mock, Stub y Spy in Spock testing y los tutoriales que he estado viendo en línea no los explican en detalle.
No entiendo la diferencia entre Mock, Stub y Spy in Spock testing y los tutoriales que he estado viendo en línea no los explican en detalle.
Respuestas:
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 simulacro es una clase ficticia que reemplaza a una real, devolviendo algo como nulo o 0 para cada llamada al método. Utilice un simulacro si necesita una instancia ficticia de una clase compleja que, de otro modo, 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 las simulaciones es que puede aislar la clase bajo prueba del resto del sistema.
Un stub es también una clase ficticia que proporciona resultados reproducidos más específicos, preparados o pregrabados para determinadas solicitudes bajo prueba. Se podría decir que un talón es una burla elegante. En Spock, a menudo leerá sobre métodos de código auxiliar.
Un espía es una especie de híbrido entre un objeto real y un código auxiliar, es decir, es básicamente el objeto real con algunos métodos (no todos) sombreados por métodos auxiliares. Los métodos no aptos se enrutan simplemente al objeto original. De esta manera, puede tener un comportamiento original para los métodos "baratos" o triviales y un comportamiento falso para los métodos "costosos" o complejos.
Actualización 2017-02-06: En realidad, la respuesta del usuario mikhail es más específica para Spock que la original anterior. Entonces, dentro del alcance de Spock, lo que describe es correcto, pero eso no falsifica mi respuesta general:
Ahora aquí hay una prueba de ejemplo ejecutable, que demuestra qué es posible y qué no. 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?")
}
}
La pregunta estaba en el contexto del marco de Spock y no creo que las respuestas actuales tomen esto en cuenta.
Basado en documentos de Spock (ejemplos personalizados, mi propia redacción agregada):
Stub: se utiliza para hacer que los colaboradores respondan a las llamadas a métodos de cierta manera. Al apuntar un método, no le importa si se va a llamar al método ni cuántas veces; solo desea que devuelva algún valor o realice algún efecto secundario, siempre que se llame.
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Simulacro: Se utiliza para describir 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 Mock puede actuar como Mock y Stub:
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 los métodos seleccionados. Puede usarse como un simulacro para describir interacciones.
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:
Evite usar Mock () si Stub () es suficiente.
Evite usar Spy () si puede, tener que hacerlo podría ser un olor y pistas de una prueba incorrecta o un diseño incorrecto del objeto bajo prueba.
En lenguaje sencillo:
Mock: te burlas de un tipo y sobre la marcha obtienes un objeto creado. Los métodos de este objeto simulado devuelven los valores predeterminados del tipo de retorno.
Stub: crea una clase stub donde los métodos se redefinen con una definición según sus requisitos. Ejemplo: en el método de objeto real, llama a una api externa y devuelve el nombre de usuario contra y la identificación. En el método de objeto stubbed devuelve un nombre ficticio.
Espía: Creas un objeto real y luego lo espías. Ahora puede burlarse de algunos métodos y optar por no hacerlo para algunos.
Una diferencia de uso es que no puede simular objetos de nivel de método. mientras que puede crear un objeto predeterminado en el método y luego espiarlo para obtener el comportamiento deseado de los métodos en el objeto espiado.
Los stubs son en realidad solo para facilitar la prueba unitaria, no son parte de la prueba. Los simulacros son parte de la prueba, parte de la verificación, parte del pasa / no pasa.
Entonces, digamos que tiene un método que toma un objeto como parámetro. Nunca haces nada que cambie este parámetro en la prueba. Simplemente lee un valor de él. Eso es un trozo.
Si cambia algo, o necesita verificar algún tipo de interacción con el objeto, entonces es una simulación.