Non capisco la differenza tra Mock, Stub e Spy nei test di Spock ei tutorial che ho visto online non li spiegano in dettaglio.
Non capisco la differenza tra Mock, Stub e Spy nei test di Spock ei tutorial che ho visto online non li spiegano in dettaglio.
Risposte:
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:
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 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:
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.
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.
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.