spock mocking stubbing
Mocking, Stubbing et espionnage avec Spock:
Test paramétré dans Spock Framework a été expliqué en détail dans ce Série de didacticiels de formation sur Spock .
La simulation et le stubbing sont l'un des éléments de base les plus essentiels des tests unitaires étendus. La prise en charge du mocking et du subbing est comme la cerise sur le gâteau pour un cadre.
Pour les frameworks existants tels que JUnit, JBehave, etc., la prise en charge des simulations et des stubs ne sort pas de la boîte, il faut donc qu'un développeur utilise des bibliothèques tierces telles que Mockito, PowerMock, EasyMock, etc. afin de les utiliser dans le tests unitaires.
Afin de comprendre les simulations et les stubs et leurs cas d'utilisation, vous pouvez jeter un coup d'œil à notre série de Tutoriel Mockito .
Dans ce didacticiel, nous en apprendrons davantage sur les fonctionnalités intégrées de mocking et de stubbing intégrées à la bibliothèque Spock elle-même, ce qui permettrait à son tour d'utiliser la syntaxe Groovy plus simple et réduit ainsi le besoin d'ajouter / d'inclure 3 autresrdbibliothèques du parti.
Vous pouvez toujours inclure d'autres frameworks Mocking dans vos tests, car tout code Java valide est également du code Groovy valide.
Ce que vous apprendrez:
- Application sous test
- Se moquer de Spock
- Stubbing à Spock
- Espionnage à Spock
- Conclusion
- Code source de l'application
- lecture recommandée
Application sous test
Définissons d'abord un exemple d'application Java, que nous allons tester à l'aide de simulacres et de stubs dans le framework Spock.
Nous travaillerons sur une application StudentGradeCalculator qui prend le score total d'une base de données abstraite pour un ID étudiant donné et a une logique simple d'attribution de notes en fonction de la valeur du score total. Nous utiliserons une interface de base de données qui a peu de méthodes pour récupérer et mettre à jour les scores et les notes des étudiants.
Le code de l'application sera disponible dans la dernière section de ce tutoriel.
Se moquer de Spock
Didacticiel vidéo
Dans cette section, nous verrons comment instancier et initialiser Mocks dans le framework Spock et comment valider les interactions sur le mock, c'est-à-dire que la validation des appels aux mocks s'est déroulée selon les attentes de la méthode testée.
Avec Mocks, vous n'avez pas à effectuer beaucoup de configurations, mais vous pouvez valider les interactions qui se sont produites avec les objets fictifs fournis à l'application testée.
Avec des simulacres, vous pouvez faire des choses comme:
- Avec quels arguments les simulacres ont-ils été appelés?
- Quel était le nombre total d'appels, etc.?
- Vérifier l'ordre des moqueries.
Voyons un exemple simple de StudentGradeCalculator, dans lequel nous fournissons l'objet d'implémentation de base de données simulé et validons les interactions avec le Mock. Nous allons essayer de comprendre les fonctionnalités moqueuses avec des exemples simples.
questions et réponses entretien ingénieur assurance qualité
Veuillez noter que toutes les validations d'interaction doivent avoir lieu dans le bloc «alors» par convention.
Ci-dessous le code de la méthode testée (qui sera appelé dans le ' lorsque: ' bloquer)
public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; }
#1) Validation des interactions avec des arguments exacts: validons d'abord les interactions avec les arguments exactement attendus. Ici, nous nous attendons à ce que les méthodes simulées soient appelées avec les arguments exacts (selon le flux d'exécution de la méthode).
Ici ' étudiantBase de données »Est le Mock d'une interface de base de données pour laquelle nous validons les interactions.
def 'illustrate mocks for interaction verification with arguments'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade('123','C') 1*studentDatabase.getStudentGrade('123') }
Comme indiqué ci-dessus, nous validons avec les arguments exacts, de sorte que l'implémentation simulée doit avoir été appelée avec. Toute modification de ces arguments entraînera l'échec du test et le journal des erreurs indique la raison appropriée.
Essayons de modifier la note dans ' updateStudentGrade »À« A »au lieu du« C »réellement appelé et voyez quelle erreur nous obtenons lorsque le test est exécuté.
Too few invocations for: 1*studentDatabase.updateStudentGrade('123','A') (0 invocations) Unmatched invocations (ordered by similarity): 1 * studentDatabase.updateStudentGrade('123', 'C') 1 * studentDatabase.getStudentScores('123')
Il affichera une erreur du type «Trop peu d'appels» car il ne peut pas trouver l'invocation Mock avec les arguments fournis.
#deux) Voyons maintenant comment valider les interactions Mock sans fournir les valeurs d'argument réelles, c'est-à-dire que ce qui nous intéresse est simplement de savoir que la simulation a été invoquée sur la méthode, mais pas avec quels arguments.
Ces types d'exigences sont les plus courants lors de l'écriture de tests unitaires pour le code de production réel, car il n'est pas toujours facile d'identifier les arguments réels qui dépendent essentiellement de la logique métier de base de l'application testée.
La syntaxe est simple, il vous suffit d'utiliser un trait de soulignement «_» pour un argument dont la valeur réelle n'est pas connue.
Par exemple, pour vérifier toute valeur de chaîne, vous pouvez simplement mentionner '_ en tant que chaîne »À la place d'un argument dans le test et il doit passer pour toute valeur String (de même pour d'autres types de données primitifs et personnalisés).
Comprenons cela avec un exemple
def 'illustrate mocks for interaction verification with generic matchers'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) 1*studentDatabase.getStudentGrade('123') }
Un point important à noter ici est que vous pouvez toujours faire des mix and match pour quels arguments sont connus et ce qui ne l'est pas. Par exemple, dans l'exemple ci-dessous, nous validons l'interaction d'une simulation avec les arguments réels et de l'autre avec les correspondances lâches.
# 3) Enfin, voyons un scénario dans lequel nous pouvons vérifier l’ordre d’appel des simulacres, c’est-à-dire l’ordre dans lequel les simulacres ont été appelés lorsque le test est exécuté.
Il est parfois essentiel de valider le flux des événements lorsque plusieurs collaborateurs / simulateurs sont impliqués dans l'application testée et il est utile de comprendre et de valider que les méthodes ont été appelées dans une séquence prédéterminée.
def 'illustrate mocks for validating order'() { when: studentReportGenerator.calculateStudentGrade('123'); then: 1*studentDatabase.getStudentGrade('123') then: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) }
Ceci peut être réalisé en utilisant simplement plusieurs blocs «alors:» dans l'ordre des attentes de séquence simulée. Si la séquence mentionnée ne correspond pas à l'ordre réel d'appel, une erreur détaillant «Mauvais ordre d'appel» est émise.
Par exemple, si je change l'ordre de ce qui précède alors , l'exécution du test générera une erreur comme indiqué ci-dessous.
Wrong invocation order for: 1*studentDatabase.updateStudentGrade(_ as String, _ as String) (1 invocation) Last invocation: studentDatabase.updateStudentGrade('123', 'C')
Stubbing à Spock
Didacticiel vidéo
lors du dépannage d'un câble réseau en direct, que devez-vous utiliser
Nous avons exploré tout ce qui concerne les moqueries, voyons maintenant comment définir des stubs sur les objets simulés. Le stubbing n'est rien d'autre que la configuration de réponses prédéfinies ou prédéfinies sur les appels Mock pour tester les différents flux / scénarios de l'application testée.
Considérez cela comme la programmation d'une simulation pour renvoyer une valeur prédéfinie lors de son appel. Nous allons continuer avec la même application StudentGradeCalculator et stuber les appels d'interface de base de données pour tester différents scénarios.
Un Stub est comme un Mock qui en quelque sorte émule le comportement de l'objet réel. Vous pouvez simplement l'appeler comme un simulacre programmé.
Syntaxe de stubbing
La syntaxe du stubbing est de 2 opérateurs de décalage vers la droite - c'est-à-dire ' >> '
Afin de définir un stub sur n'importe quel appel, vous pouvez le définir comme suit:
StubbedObject.StubbedMethod(//argumentList) >> “Stubbed Response”
Voyons maintenant les différents scénarios de stubbing avec des exemples.
#1) Stubbing avec les paramètres réels: Si les arguments sont connus à l'avance ou si vous souhaitez définir le stub uniquement lorsque l'appel est avec des arguments spécifiés, cette façon de spécifier les stubs peut être utilisée.
def 'illustrate stubs with exact matchers'() { given: studentDatabase.getStudentScores('123') >> (20F, 30F, 50F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' }
Ici, vous pouvez voir que le stub a été défini avec un argument exact, c'est-à-dire StudentId dans ce cas comme «123» (pour toute autre valeur, le stub ne sera pas appelé et une réponse par défaut sera renvoyée).
# 2) Stubbing avec des matchers indulgents: Si les arguments ne sont pas connus (ou ne sont pas importants), alors nous pouvons les mentionner vaguement comme nous l'avons fait pour les mocks et la syntaxe reste la même c'est-à-dire le trait de soulignement «_».
def 'illustrate stubs with loose matchers'() { given: studentDatabase.getStudentScores(_ as String) >> (20F, 30F, 10F) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' }
# 3) Voyons un autre exemple rapide où nous avons configuré le stub pour lever une exception.
Ces scénarios sont très utiles pour valider la logique de gestion des erreurs d'une application testée (comme dans le monde réel, générer toutes les exceptions n'est en fait pas possible, mais un simple stub pourrait être configuré pour renvoyer l'exception que nous voulons, puis l'affirmer dans le bloc alors).
def 'illustrate stubs with exceptions thrown'() { given: studentDatabase.getStudentScores(_ as String) >> {throw new RuntimeException()} when: studentReportGenerator.calculateStudentGrade('123') then: thrown(RuntimeException.class) }
Espionnage à Spock
Les espions sont basés sur des objets réels c'est-à-dire qu'ils ont besoin de l'implémentation de l'interface et non de l'interface abstraite elle-même. Les espions sont puissants et ils peuvent vous permettre d'obtenir de vraies méthodes appelées pour l'application testée et de vérifier quels arguments les méthodes ont été appelées.
Les espions permettent également de définir des simulations partielles sur les instances d'objets espionnés. c'est-à-dire que vous voulez définir le comportement de certaines méthodes sur l'objet, alors vous pouvez et autorisez le reste à être appelé comme des appels de méthode réels.
Celles-ci sont généralement utiles dans une situation où certaines méthodes d'interface ne sont pas implémentées et il y en a peu d'autres qui sont entièrement fonctionnelles. Par conséquent, en tant que développeur, vous pouvez choisir de remplacer celles non implémentées et d'appeler les implémentations réelles des méthodes fonctionnelles.
Il convient de noter que, pour les objets Spied, à moins que des stubs ne soient définis, le comportement par défaut sera d'appeler l'implémentation réelle. Cela dit, les espions ne devraient pas être fréquemment appelés et toute la couverture du scénario peut être réalisée en utilisant des simulacres et des talons et une combinaison de ceux-ci.
Voyons quelques exemples d'utilisation de Spies dans le framework Spock en utilisant le même exemple de StudentGradeCalculator (Nous avons créé une véritable implémentation de la Base de données des étudiants qui est une implémentation en mémoire utilisant HashMap pour illustrer l'appel de méthodes réelles et le renvoi de données. Le code sera disponible dans la dernière section du tutoriel):
# 1) Espionnage en utilisant une combinaison d'appels de méthode stub et réels
def 'illustrate spies'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'A' 1*spiedStudentDatabase.getStudentGrade(_ as String) >> 'A' }
L'exemple ci-dessus illustre la syntaxe de création de Spy à l'aide du framework Spock. Le stub est défini au moment de la déclaration lui-même.
En outre, les appels espionnés peuvent être vérifiés comme illustré dans le bloc then (avec des concordants d'arguments lâches qui peuvent être définis pour tout argument spécifique).
# 2) Espionnage en utilisant tous les appels de méthode réels
def 'illustrate spies with real method call'() { given: StudentDatabase spiedStudentDatabase = Spy(StudentDatabase.class) def studentReportGenerator = new StudentReportGenerator(spiedStudentDatabase) when: def grade = studentReportGenerator.calculateStudentGrade('123') then: grade == 'C' 1*spiedStudentDatabase.getStudentGrade('123') }
Dans l'exemple ci-dessus, comme nous n'avons mentionné aucun comportement stubbed, tous les appels iront à l'implémentation réelle.
Conclusion
Dans ce didacticiel, nous avons tout appris sur les techniques intégrées de Mock Stub et Spy à l'aide du framework Spock. Spock facilite les choses en combinant ces fonctionnalités dans le cadre du framework lui-même avec une syntaxe groovy plus lisible avec le code standard moins important.
Les simulacres, les stubs et les espions sont largement utilisés dans les tests unitaires pour augmenter la couverture et tester ou valider la logique métier de base de l'application testée.
Code source de l'application
StudentReportGenerator.java - c'est la méthode / l'application testée
package app.studentScores; import java.util.List; public class StudentReportGenerator { public IStudentDatabase studentDatabase; public StudentReportGenerator(IStudentDatabase studentDatabase) { this.studentDatabase = studentDatabase; } public String calculateStudentGrade(String studentId) { String grade; // check if grade is already there in database grade = studentDatabase.getStudentGrade(studentId); if(grade!=null && !grade.isEmpty()) { return grade; } List scoreList = studentDatabase.getStudentScores(studentId); Float totalScore = 0F; if(scoreList !=null) totalScore = scoreList.stream().reduce(0F,(a,b)->a+b); if(totalScore > 90) { grade = 'A'; } else if (totalScore > 80) { grade = 'B'; } else { grade = 'C'; } // update the calculated grade in database studentDatabase.updateStudentGrade(studentId, grade); return grade; } }
IStudentDatabase.java - Interface de base de données
package app.studentScores; import java.util.List; public interface IStudentDatabase { List getStudentScores(String studentId); void updateStudentGrade(String studentId, String grade); String getStudentGrade(String studentId); }
StudentDatabase.java - Implémentation InMemory de l'interface IStudentDatabase.java
package app.studentScores; import java.util.*; public class StudentDatabase implements IStudentDatabase { private Map scoreMap; private Map gradeMap; public StudentDatabase() { this.scoreMap = new HashMap(); this.gradeMap = new HashMap(); scoreMap.put('123', Arrays.asList(40F, 30F, 30F)); scoreMap.put('456', Arrays.asList(10F, 10F, 30F)); gradeMap.put('123', 'C'); gradeMap.put('456', 'A'); } @Override public List getStudentScores(String studentId) { return scoreMap.get(studentId); } @Override public void updateStudentGrade(String studentId, String grade) { gradeMap.put(studentId,grade); } @Override public String getStudentGrade(String studentId) { return gradeMap.get(studentId); } }
Dans notre prochain tutoriel, nous verrons comment intégrer le framework Spock à d'autres frameworks et technologies de test.
Tutoriel PREV | Tutoriel SUIVANT
lecture recommandée
- Ecrire des tests unitaires avec Spock Framework
- Questions d'entrevue Spock avec réponses (les plus populaires)
- Spock pour l'intégration et les tests fonctionnels avec le sélénium
- Tests basés sur les données ou paramétrés avec Spock Framework
- Tutoriel Spock: Test avec Spock et Groovy
- Meilleure série de didacticiels C # GRATUITS: Le guide C # ultime pour les débutants
- Test de charge avec les didacticiels HP LoadRunner
- Fonctions de date et d'heure en C ++ avec des exemples