Je ne comprends pas la différence entre les tests Mock, Stub et Spy dans les tests Spock et les didacticiels que j'ai consultés en ligne ne les expliquent pas en détail.
Je ne comprends pas la différence entre les tests Mock, Stub et Spy dans les tests Spock et les didacticiels que j'ai consultés en ligne ne les expliquent pas en détail.
Réponses:
Attention: je vais trop simplifier et peut-être même légèrement falsifier dans les paragraphes à venir. Pour plus d'informations, consultez le site Web de Martin Fowler .
Un simulacre est une classe factice remplaçant une vraie, renvoyant quelque chose comme null ou 0 pour chaque appel de méthode. Vous utilisez une simulation si vous avez besoin d'une instance factice d'une classe complexe qui, autrement, utiliserait des ressources externes telles que des connexions réseau, des fichiers ou des bases de données ou utiliserait peut-être des dizaines d'autres objets. L'avantage des simulations est que vous pouvez isoler la classe testée du reste du système.
Un stub est également une classe factice fournissant des résultats plus spécifiques, préparés ou préenregistrés, rejoués à certaines requêtes en cours de test. On pourrait dire qu'un talon est un simulacre de fantaisie. Dans Spock, vous lirez souvent sur les méthodes de stub.
Un espion est une sorte d'hybride entre un objet réel et un stub, c'est-à-dire qu'il s'agit essentiellement de l'objet réel avec certaines méthodes (pas toutes) masquées par des méthodes de stub. Les méthodes non stubbed sont simplement acheminées vers l'objet d'origine. De cette façon, vous pouvez avoir un comportement original pour les méthodes «bon marché» ou triviales et un faux comportement pour les méthodes «coûteuses» ou complexes.
Mise à jour 2017-02-06: En fait, la réponse de l'utilisateur Mikhail est plus spécifique à Spock que ma réponse originale ci-dessus. Donc, dans le cadre de Spock, ce qu'il décrit est correct, mais cela ne fausse pas ma réponse générale:
Voici maintenant un exemple de test exécutable, démontrant ce qui est possible et ce qui ne l'est pas. C'est un peu plus instructif que les extraits de mikhail. Un grand merci à lui de m'avoir inspiré pour améliorer ma propre réponse! :-)
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 question se situait dans le contexte du cadre Spock et je ne pense pas que les réponses actuelles en tiennent compte.
Basé sur des documents Spock (exemples personnalisés, ma propre formulation a été ajoutée):
Stub: utilisé pour que les collaborateurs répondent aux appels de méthode d'une certaine manière. Lorsque vous stubbing une méthode, vous ne vous souciez pas de savoir si et combien de fois la méthode sera appelée; vous voulez juste qu'il renvoie une valeur, ou effectue un effet secondaire, chaque fois qu'il est appelé.
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Mock: utilisé pour décrire les interactions entre l'objet sous spécification et ses collaborateurs.
def "should send message to subscriber"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") // subscriber is a Mock()
}
Un Mock peut agir comme un Mock et un Stub:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Spy: est toujours basé sur un objet réel avec des méthodes originales qui font de vraies choses. Peut être utilisé comme un stub pour modifier les valeurs de retour des méthodes de sélection. Peut être utilisé comme une maquette pour décrire les interactions.
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
}
Résumé:
Évitez d'utiliser Mock () si Stub () est suffisant.
Évitez d'utiliser Spy () si vous le pouvez, car cela pourrait être une odeur et des indices de test incorrect ou de conception incorrecte de l'objet testé.
En termes simples:
Mock: Vous vous moquez d'un type et à la volée vous obtenez un objet créé. Les méthodes de cet objet fictif retournent les valeurs par défaut du type de retour.
Stub: vous créez une classe de stub où les méthodes sont redéfinies avec la définition selon vos besoins. Ex: Dans la méthode de l'objet réel, vous appelez une API externe et renvoyez le nom d'utilisateur et l'id. Dans la méthode d'objet stubbed, vous retournez un nom factice.
Spy: Vous créez un objet réel et ensuite vous l'espionnez. Vous pouvez maintenant vous moquer de certaines méthodes et choisir de ne pas le faire pour certaines.
Une différence d'utilisation est que vous ne pouvez pas simuler des objets de niveau méthode. alors que vous pouvez créer un objet par défaut dans method, puis l'espionner pour obtenir le comportement souhaité des méthodes dans l'objet espionné.
Les stubs ne servent en réalité qu'à faciliter le test unitaire, ils ne font pas partie du test. Les simulacres, font partie du test, font partie de la vérification, font partie de la réussite / échec.
Donc, disons que vous avez une méthode qui prend un objet comme paramètre. Vous ne faites jamais rien qui modifie ce paramètre dans le test. Vous en lisez simplement une valeur. C'est un bout.
Si vous changez quoi que ce soit, ou devez vérifier une sorte d'interaction avec l'objet, alors c'est une simulation.