Mocks, stubs and spies in the Spock Framework

Spock fournit 3 outils puissants (mais différents dans leur essence) qui simplifient les tests d'écriture: Mock, Stub et Spy.



Très souvent, le code qui doit être testé doit interagir avec des modules externes appelés dépendances (l'article d'origine utilise le terme collaborateurs, ce qui n'est pas très courant dans l'environnement de langue russe).


Les tests unitaires sont le plus souvent conçus pour tester une seule classe isolée à l'aide de diverses variantes de mok: Mock, Stub et Spy. Les tests seront donc plus fiables et moins susceptibles de se casser à mesure que le code de dépendance évolue.


De tels tests isolés sont moins sujets aux problèmes lors de la modification des détails internes de l'implémentation des dépendances.


D'un traducteur: chaque fois que j'utilise Spock Framework pour écrire des tests, je sens que je peux faire une erreur en choisissant un moyen de remplacer les dépendances. Cet article a la feuille de triche la plus courte possible pour choisir un mécanisme de création de mokas.


TL; DR


Se moquer


Utilisez Mock pour:


  • contrôles de contrat entre le code de test et les dépendances
  • vérifier que les méthodes de dépendance sont appelées le nombre correct de fois
  • validation des paramètres avec lesquels le code de dépendance est appelé

Bouts


Utilisez Stub pour:


  • fournir des résultats d'appel prédéfinis
  • effectuer des actions prédéfinies attendues des dépendances, telles que lever des exceptions

Espions


Craignez les espions. Comme le dit la documentation de Spock:


Réfléchissez bien avant d'utiliser ce mécanisme. Vous devriez peut-être repenser votre solution et réorganiser votre code.

Mais il se trouve que dans certaines situations, nous devons travailler avec du code hérité. Le code hérité peut être difficile, voire impossible, à tester avec des mobs et des talons. Dans ce cas, il n'y a qu'une seule solution: utiliser Spy.


Il vaut mieux que le code hérité soit couvert de tests utilisant Spy que de ne pas avoir de tests hérités du tout.


Utilisez Spy pour:


  • tester le code hérité qui ne peut pas être testé par d'autres méthodes
  • vérifier que les méthodes de dépendance sont appelées le nombre correct de fois
  • validation des paramètres passés
  • fournir une réponse de dépendance prédéfinie
  • effectuer des actions prédéfinies en réponse à des appels à des méthodes de dépendance

Se moquer



Toute la puissance de moxas se manifeste lorsque la tâche d'un test unitaire est de vérifier le contrat entre le code testé et les dépendances. Regardons l'exemple suivant, où nous avons un contrôleur FooController qui utilise FooService comme dépendance, et testons cette fonctionnalité avec des mobs.


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

Dans ce scénario, nous voulons écrire un test qui testera:


  • contrat entre FooController et FooService
  • FooService.doSomething(name) est appelé le bon nombre de fois
  • FooService.doSomething(name) est appelé avec le paramètre correct

Jetez un œil au test:


MockSpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class MockSpec extends Specification implements ControllerUnitTest<FooController> { void "Mock FooService"() { given: "  " def fooService = Mock(FooService) and: "    " controller.fooService = fooService when: "  " controller.doSomething() then: "         " 1 * fooService.doSomething("Sally") and: "  ''    - 'null'" response.text == null.toString() } } 

Le test ci-dessus crée une maquette de service:


 def fooService = Mock(FooService) 

Le test vérifie également que FooService.doSomething(name) est appelé une fois et que le paramètre qui lui est transmis correspond à la chaîne "Sally" .


 1 * fooService.doSomething("Sally") 

Le code ci-dessus résout 4 problèmes importants:


  • crée une maquette pour FooService
  • vérifie que FooService.doSomething(String name) est appelé exactement une fois avec le paramètre String et la valeur "Sally"
  • isole le code de test, remplaçant l'implémentation des dépendances

Bouts


Le code testé utilise-t-il des dépendances? Le but du test est-il de s'assurer que le code testé fonctionne correctement lors de l'interaction avec les dépendances? Les résultats des appels aux méthodes de dépendance sont-ils des valeurs d'entrée pour le code testé?


Si le comportement du code testé change en fonction du comportement des dépendances, vous devez utiliser des stubs (Stub).


Examinons l'exemple suivant avec FooController et FooService et testons la fonctionnalité du contrôleur à l'aide de stubs.


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

Code de test:


StubSpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class StubSpec extends Specification implements ControllerUnitTest<FooController> { void "Stub FooService"() { given: "  " def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" } and: "    " controller.fooService = fooService when: "  " controller.doSomething() then: "   " // 1 * fooService.doSomething() //        response.text == "Stub did something" } } 

Vous pouvez créer un stub comme celui-ci:


 def fooService = Stub(FooService) { doSomething(_) >> "Stub did something" } 

Le code ci-dessus résout 4 problèmes importants:


  • crée un stub FooService
  • s'assure que FooService.doSomething(String name) renverra la chaîne "Stub did something" quel que soit le paramètre passé (nous avons donc utilisé le caractère _ )
  • isole le code testé, en remplaçant l'implémentation des dépendances par un stub

Espions


Veuillez ne pas lire cette section.


Ne regarde pas.


Passez à la suivante.


Toujours en train de lire? Eh bien, d'accord, parlons de Spy.



N'utilisez pas Spy. Comme le dit la documentation de Spock:


Réfléchissez bien avant d'utiliser ce mécanisme. Vous devriez peut-être repenser votre solution et réorganiser votre code.

Dans ce cas, il existe des situations où nous devons travailler avec du code hérité. Le code hérité ne peut pas être testé à l'aide de mobs ou de talons. Dans ce cas, l'espion reste la seule option viable.


Les espions sont différents des mokas ou des talons car ils ne fonctionnent pas comme des talons.


Lorsqu'une dépendance est remplacée par un mok ou un stub, un objet de test est créé et le code source réel de la dépendance n'est pas exécuté.


L'espion, d'autre part, exécutera le code source principal de la dépendance pour laquelle l'espion a été créé, mais l'espion vous permettra de modifier ce que renvoie l'espion et de vérifier les appels de méthode, tout comme les mokas et les talons. (D'où le nom Spy).


Regardons l'exemple FooController suivant, qui utilise FooService , puis testons la fonctionnalité avec un espion.


FooController.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooController { FooService fooService def doSomething() { render fooService.doSomething("Sally") } } 

FooService.groovy


 package com.mycompany.myapp import groovy.transform.CompileStatic @CompileStatic class FooService { String doSomething(String name) { "Hi ${name}, FooService did something" } } 

Code de test:


SpySpec.groovy


 package com.mycompany.myapp import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class SpySpec extends Specification implements ControllerUnitTest<FooController> { void "Spy FooService"() { given: " -" def fooService = Spy(FooService) and: "   " controller.fooService = fooService when: "  " controller.doSomething() then: "     " 1 * fooService.doSomething("Sally") >> "A Spy can modify implementation" and: '     ' response.text == "A Spy can modify implementation" } } 

La création d'une instance d'espionnage est assez simple:


 def fooService = Spy(FooService) 

Dans le code ci-dessus, l'espion nous permet de vérifier l'appel à FooService.doSomething(name) , le nombre d'appels et les valeurs des paramètres. De plus, l'espion change la mise en œuvre de la méthode pour renvoyer une valeur différente.


 1 * fooService.doSomething("Sally") >> "A Spy can modify implementation" 

Le code ci-dessus résout 4 problèmes importants:


  • crée une instance d'espionnage pour FooService
  • vérifie l'interaction des dépendances
  • vérifie le fonctionnement de l'application en fonction de certains résultats d'appels de méthode de dépendance
  • isole le code testé, en remplaçant l'implémentation des dépendances par un stub

FAQ


Quelle option utiliser: Mock, Stub ou Spy?


C'est une question à laquelle de nombreux développeurs sont confrontés. Cette FAQ peut vous aider si vous n'êtes pas sûr de l'approche à utiliser.


Q: Le but du test est-il la vérification du contrat entre le code testé et les dépendances?


R: Si vous avez répondu Oui, utilisez Mock


Q: Le but du test est-il de s'assurer que le code testé fonctionne correctement lors de l'interaction avec les dépendances?


R: Si vous avez répondu Oui, utilisez Stub


Q: Les résultats de l'appel des méthodes de dépendance sont-ils des valeurs d'entrée pour le code testé?


R: Si vous avez répondu Oui, utilisez Stub


Q: Travaillez-vous avec du code hérité, qui est très difficile à tester, et vous n'avez plus d'options?


A: Essayez d'utiliser Spy


Exemple de code


Vous pouvez trouver le code pour tous les exemples dans cet article à:


https://github.com/ddelponte/mock-stub-spy


Liens utiles


Source: https://habr.com/ru/post/fr435488/


All Articles