J’écris des cas de test jUnit à 3 fins:
Je ne comprends pas pourquoi ou quand Mockito.verify()
devrait être utilisé. Lorsque je vois verify()
être appelé, il me dit que mon jUnit prend conscience de l’implémentation. (Ainsi, modifier mon implémentation briserait mes jUnits, même si mes fonctionnalités n’étaient pas affectées).
Je cherche:
Quelles devraient être les directives pour une utilisation appropriée de Mockito.verify()
?
Est-il fondamentalement correct que jUnits connaisse ou soit étroitement lié à l’implémentation de la classe testée?
Si le contrat de la classe A inclut le fait qu’il appelle la méthode B d’un object de type C, alors vous devriez tester cela en créant une maquette de type C et en vérifiant que la méthode B a été appelée.
Cela implique que le contrat de la classe A a suffisamment de détails pour parler du type C (qui peut être une interface ou une classe). Donc, oui, nous parlons d’un niveau de spécification qui va au-delà de la simple “exigence système” et qui décrit en partie l’implémentation.
Ceci est normal pour les tests unitaires. Lorsque vous effectuez des tests unitaires, vous voulez vous assurer que chaque unité fait la “bonne chose”, et cela inclut généralement ses interactions avec les autres unités. “Unités” peut signifier des classes ou des sous-ensembles plus importants de votre application.
Mettre à jour:
Je pense que cela ne s’applique pas uniquement à la vérification, mais aussi à la dénonciation. Dès que vous modifiez une méthode d’une classe de collaborateur, votre test unitaire dépend en quelque sorte de la mise en œuvre. C’est un peu dans la nature des tests unitaires pour être ainsi. Comme Mockito concerne autant le stubbing que la vérification, le fait que vous utilisiez Mockito implique que vous allez rencontrer ce type de dépendance.
D’après mon expérience, si je change l’implémentation d’une classe, je dois souvent modifier l’implémentation de ses tests unitaires pour correspondre. En règle générale, cependant, je n’aurai pas à modifier l’inventaire des tests unitaires pour la classe. À moins bien sûr, la raison de ce changement était l’existence d’une condition que je n’ai pas pu tester plus tôt.
Donc, voici ce que sont les tests unitaires. Un test qui ne souffre pas de ce type de dépendance sur la manière dont les classes de collaborateurs sont utilisées est en réalité un test de sous-système ou un test d’intégration. Bien sûr, ceux-ci sont fréquemment écrits avec JUnit, et impliquent fréquemment l’utilisation de moqueur. À mon avis, “JUnit” est un nom terrible pour un produit qui nous permet de produire tous les différents types de tests.
La réponse de David est bien sûr correcte mais n’explique pas très bien pourquoi vous voudriez cela.
Fondamentalement, lors des tests unitaires, vous testez une unité de fonctionnalité de manière isolée. Vous testez si l’entrée produit la sortie attendue. Parfois, vous devez également tester les effets secondaires. En bref, vérifier vous permet de le faire.
Par exemple, vous avez un peu de logique métier qui est censé stocker des choses à l’aide d’un DAO. Vous pouvez le faire en utilisant un test d’intégration qui instancie le DAO, le connecte à la logique métier, puis fouille dans la firebase database pour voir si les éléments attendus ont été stockés. Ce n’est plus un test unitaire.
Ou, vous pouvez simuler le DAO et vérifier qu’il est appelé comme prévu. Avec mockito, vous pouvez vérifier que quelque chose est appelé, à quelle fréquence il est appelé, et même utiliser des comparateurs sur les parameters pour vous assurer qu’il est appelé d’une manière particulière.
Le revers du test unitaire comme celui-ci est en effet que vous associez les tests à l’implémentation, ce qui rend le refactoring un peu plus difficile. D’un autre côté, une bonne odeur de conception est la quantité de code nécessaire pour l’exercer correctement. Si vos tests doivent être très longs, il y a probablement quelque chose qui ne va pas dans la conception. Donc, le code avec beaucoup d’effets secondaires / interactions complexes qui doivent être testés n’est probablement pas une bonne chose à avoir.
C’est une bonne question! Je pense que la cause profonde est la suivante, nous utilisons JUnit non seulement pour les tests unitaires. Donc, la question devrait être divisée:
donc, si nous ignorons les tests supérieurs à l’unité, la question peut être reformulée “L’ utilisation de tests unitaires avec Mockito.verify () crée un couple important entre le test unitaire et mon implémentation possible, puis-je créer des ” boîtes grises “? “ tests unitaires et quelles règles je devrais utiliser pour cela “.
Passons maintenant à toutes ces étapes.
* – Dois-je utiliser Mockito.verify () dans mes tests d’ intégration (ou dans tout autre test supérieur à l’unité)? Je pense que la réponse est clairement non. De plus, vous ne devriez pas utiliser de simulacre pour cela. Votre test doit être aussi proche que possible de l’application réelle. Vous testez un cas d’utilisation complet et non une partie isolée de l’application.
* black-box vs white-box unit-testing * Si vous utilisez une approche en boîte noire , que faites-vous réellement? Vous fournissez (toutes les classes d’équivalence) une entrée, un état et des tests que vous recevrez. Dans cette approche, l’utilisation de mocks en général est justifiée (vous ne faites qu’imiter qu’ils font ce qu’il faut, vous ne voulez pas les tester), mais appeler Mockito.verify () est superflu.
Si vous utilisez l’approche de la boîte blanche , qu’est-ce que vous faites réellement, vous testez le comportement de votre unité. Dans cette approche, appeler à Mockito.verify () est essentiel, vous devez vérifier que votre unité se comporte comme prévu.
règles de base pour le test de la boîte grise Le problème avec le test de la boîte blanche est qu’il crée un couplage élevé. Une solution possible consiste à faire des tests en boîte grise, et non des tests en boîte blanche. C’est une sorte de combinaison de tests de boîte noire et blanche. Vous êtes vraiment en train de tester le comportement de votre unité comme dans les tests en boîte blanche, mais en général, vous le rendez indépendant de la mise en œuvre lorsque cela est possible . Lorsque c’est possible, vous allez simplement faire une vérification comme dans les boîtes noires, simplement affirmer que la sortie est ce que vous êtes censé être. Donc, l’essence de votre question est quand c’est possible.
C’est vraiment difficile. Je n’ai pas un bon exemple, mais je peux vous donner des exemples. Dans le cas qui a été mentionné ci-dessus avec equals () vs equalsIgnoreCase (), vous ne devriez pas appeler Mockito.verify (), assignez simplement la sortie. Si vous ne pouviez pas le faire, décomposez votre code dans l’unité plus petite, jusqu’à ce que vous puissiez le faire. D’un autre côté, supposons que vous ayez un service @ et que vous écrivez @ Web-Service qui est essentiellement encapsulé sur votre @Service – il délègue tous les appels au service @ (et effectue une gestion supplémentaire des erreurs). Dans ce cas, il est essentiel d’appeler Mockito.verify (), vous ne devez pas dupliquer toutes les vérifications que vous avez effectuées pour la @Serive, il suffit de vérifier que vous appelez @Service avec la liste de parameters correcte.
Je dois dire que du sharepoint vue d’une approche classique, vous avez tout à fait raison:
Il est important de se rappeler qu’il n’y a pas d’outils universels. Le type de logiciel, sa taille, les objectives de l’entreprise et la situation du marché, les compétences de l’équipe et de nombreux autres facteurs influent sur la décision concernant l’approche à utiliser dans votre cas particulier.
Comme l’ont dit certaines personnes
En ce qui concerne votre souci de ne pas tester vos tests lors de la refactorisation, cela est quelque peu prévisible lorsque vous utilisez des simulacres, des talons ou des espions. Je veux dire par définition et non en ce qui concerne une implémentation spécifique telle que Mockito. Mais vous pouvez penser de cette manière: si vous devez effectuer un refactoring qui entraînerait des changements majeurs sur le fonctionnement de votre méthode, il est conseillé de le faire en utilisant une approche TDD, ce qui signifie que vous pouvez d’abord modifier votre test pour définir la méthode. nouveau comportement (qui échouera le test), puis effectuez les modifications et obtenez le test réussi à nouveau.