J'écris des cas de test jUnit pour 3 raisons:
- Pour m'assurer que mon code satisfait toutes les fonctionnalités requises, sous toutes (ou la plupart) des combinaisons / valeurs d'entrée.
- Pour m'assurer que je peux modifier l'implémentation et me fier aux cas de test JUnit pour me dire que toutes mes fonctionnalités sont toujours satisfaites
- En tant que documentation de tous les cas d'utilisation que mon code gère, et sert de spécification pour la refactorisation - si le code devait jamais être réécrit. (Refactorisez le code, et si mes tests jUnit échouent - vous avez probablement manqué un cas d'utilisation).
Je ne comprends pas pourquoi ni quand Mockito.verify()
doit être utilisé. Quand je vois verify()
être appelé, cela me dit que mon jUnit prend conscience de l'implémentation. (Ainsi, changer mon implémentation casserait mes jUnits, même si ma fonctionnalité n'était pas affectée).
Je cherche:
Quelles devraient être les directives pour une utilisation appropriée de
Mockito.verify()
?Est-il fondamentalement correct que jUnits soit au courant ou étroitement lié à la mise en œuvre de la classe testée?
java
unit-testing
junit
mockito
Russell
la source
la source
Réponses:
Si le contrat de classe A inclut le fait qu'il appelle la méthode B d'un objet de type C, vous devez le tester en faisant une maquette de type C et en vérifiant que la méthode B a été appelée.
Cela implique que le contrat de classe A a suffisamment de détails pour qu'il parle de type C (qui peut être une interface ou une classe). Donc, oui, nous parlons d'un niveau de spécification qui va au-delà des «exigences système» et décrit en quelque sorte 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» ici peut signifier des classes ou des sous-ensembles plus importants de votre application.
Mettre à jour:
Je pense que cela ne s'applique pas seulement à la vérification, mais aussi au stubbing. Dès que vous écrivez une méthode d'une classe de collaborateur, votre test unitaire est devenu, dans un certain sens, dépendant de l'implémentation. C'est un peu dans la nature des tests unitaires de l'être. Étant donné que Mockito concerne autant le stubbing que la vérification, le fait que vous utilisez 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 changer 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 qu'il y a pour la classe; sauf bien sûr, la raison du changement était l'existence d'une condition que je n'ai pas testée plus tôt.
Voilà donc à quoi servent les tests unitaires. Un test qui ne souffre pas de ce type de dépendance sur la façon dont les classes collaboratrices 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 également fréquemment écrits avec JUnit et impliquent fréquemment l'utilisation de la moquerie. À mon avis, "JUnit" est un nom terrible, pour un produit qui nous permet de produire tous les différents types de tests.
la source
equals()
ouequalsIgnoreCase()
ne serait jamais quelque chose qui était spécifié dans les exigences d'une classe, n'aurait donc jamais de test unitaire en soi. Cependant, "fermer la connexion DB une fois terminée" (quoi que cela signifie en termes d'implémentation) peut bien être une exigence d'une classe, même si ce n'est pas une "exigence métier". Pour moi, cela se résume à la relation entre le contrat ...La réponse de David est bien sûr correcte, mais n'explique pas très bien pourquoi vous voudriez cela.
Fondamentalement, lors du test unitaire, 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 un mot, vérifiez 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 base de données pour voir si le contenu attendu a été stocké. Ce n'est plus un test unitaire.
Ou, vous pouvez vous moquer du DAO et vérifier qu'il est appelé de la manière attendue. Avec mockito, vous pouvez vérifier que quelque chose est appelé, à quelle fréquence il est appelé, et même utiliser des égaliseurs sur les paramètres pour vous assurer qu'il est appelé d'une manière particulière.
L'inconvénient des tests unitaires comme celui-ci est en effet que vous liez les tests à la mise en œuvre, ce qui rend la refactorisation 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 un problème avec la conception. Donc, le code avec beaucoup d'effets secondaires / interactions complexes qui doivent être testés n'est probablement pas une bonne chose.
la source
C'est une excellente question! Je pense que la cause première est la suivante, nous utilisons JUnit non seulement pour les tests unitaires. La question devrait donc être divisée:
Donc, si nous ignorons les tests supérieurs à l'unité, la question peut être reformulée "L' utilisation de tests unitaires en boîte blanche avec Mockito.verify () crée un grand couple entre le test unitaire et mon implémentation pourrait, puis-je en faire " une boîte grise msgstr " tests unitaires et règles générales à utiliser pour cela ".
Passons maintenant en revue toutes ces étapes.
* - Dois-je utiliser Mockito.verify () dans mes tests d' intégration (ou tout autre test supérieur à l'unité)? * Je pense que la réponse est clairement non, de plus vous ne devriez pas utiliser de maquette pour cela. Votre test doit être aussi proche que possible de l'application réelle. Vous testez un cas d'utilisation complet, pas une partie isolée de l'application.
* tests unitaires boîte noire vs boîte blanche * Si vous utilisez l' approche boîte noire , que faites-vous réellement, vous fournissez une entrée (toutes les classes d'équivalence), un état et des tests qui vous permettront d'obtenir la sortie attendue. Dans cette approche, l'utilisation de simulacres en général est justifiée (vous imitez simplement qu'ils font la bonne chose; vous ne voulez pas les tester), mais appeler Mockito.verify () est superflu.
Si vous utilisez l' approche en boîte blanche de 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 générales pour les tests en boîte grise Le problème avec les tests en boîte blanche est qu'il crée un couplage élevé. Une solution possible est de faire des tests en boîte grise, pas des tests en boîte blanche. Il s'agit en quelque sorte d'une combinaison de tests en boîte noire et blanche. Vous testez vraiment le comportement de votre unité comme dans les tests en boîte blanche, mais en général vous le rendez indépendant de l'implémentation lorsque cela est possible . Lorsque cela est possible, vous effectuerez simplement une vérification comme dans le cas de la boîte noire, affirmant simplement que la sortie est ce que vous attendez. Donc, l'essence de votre question est de savoir quand c'est possible.
C'est vraiment difficile. Je n'ai pas de bon exemple, mais je peux vous donner des exemples. Dans le cas mentionné ci-dessus avec equals () vs equalsIgnoreCase (), vous ne devez pas appeler Mockito.verify (), il suffit d'affirmer la sortie. Si vous ne pouvez pas le faire, décomposez votre code en une 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 un wrapper sur votre @Service - il délègue tous les appels au @Service (et fait une gestion supplémentaire des erreurs). Dans ce cas, appeler à Mockito.verify () est essentiel, vous ne devez pas dupliquer toutes vos vérifications que vous avez faites pour @Serive, vérifier que vous appelez à @Service avec la liste de paramètres correcte est suffisant.
la source
Je dois dire que vous avez absolument raison du point de vue d'une approche classique:
Il est important de se rappeler qu'il n'y a pas d'outils universels. Le type de logiciel, sa taille, les objectifs de l'entreprise et la situation du marché, les compétences de l'équipe et bien d'autres choses influencent la décision sur l'approche à utiliser dans votre cas particulier.
la source
Comme certains l'ont dit
En ce qui concerne votre souci de casser vos tests lors de la refactorisation, cela est quelque peu attendu lors de l'utilisation de simulacres / talons / espions. Je veux dire que par définition et non en ce qui concerne une mise en œuvre spécifique telle que Mockito. Mais vous pourriez penser de cette façon - si vous avez besoin de faire un refactoring qui créerait des changements majeurs sur la façon dont votre méthode fonctionne, il est une bonne idée de le faire sur une approche TDD, ce qui signifie que vous pouvez modifier votre test d' abord pour définir la nouveau comportement (qui échouera au test), puis effectuez les modifications et réussissez à nouveau le test.
la source
Dans la plupart des cas, lorsque les gens n'aiment pas utiliser Mockito.verify, c'est parce qu'il est utilisé pour vérifier tout ce que l'unité testée fait et cela signifie que vous devrez adapter votre test si quelque chose change. Mais je ne pense pas que ce soit un problème. Si vous voulez pouvoir changer ce qu'une méthode fait sans avoir besoin de changer son test, cela signifie essentiellement que vous voulez écrire des tests qui ne testent pas tout ce que fait votre méthode, parce que vous ne voulez pas qu'elle teste vos modifications . Et c'est la mauvaise façon de penser.
Ce qui est vraiment un problème, c'est si vous pouvez modifier ce que fait votre méthode et qu'un test unitaire censé couvrir entièrement la fonctionnalité n'échoue pas. Cela signifierait que quelle que soit l'intention de votre changement, le résultat de votre changement n'est pas couvert par le test.
Pour cette raison, je préfère me moquer autant que possible: moquer aussi vos objets de données. En faisant cela, vous pouvez non seulement utiliser la vérification pour vérifier que les méthodes correctes des autres classes sont appelées, mais également que les données transmises sont collectées via les méthodes correctes de ces objets de données. Et pour le terminer, vous devez tester l'ordre dans lequel les appels se produisent. Exemple: si vous modifiez un objet entité db puis l'enregistrez à l'aide d'un référentiel, il ne suffit pas de vérifier que les setters de l'objet sont appelés avec les données correctes et que la méthode de sauvegarde du référentiel est appelée. S'ils sont appelés dans le mauvais ordre, votre méthode ne fait toujours pas ce qu'elle doit faire. Donc, je n'utilise pas Mockito.verify mais je crée un objet inOrder avec tous les mocks et j'utilise inOrder.verify à la place. Et si vous voulez le terminer, vous devez également appeler Mockito. vérifierNoMoreInteractions à la fin et lui passer toutes les moqueries. Sinon, quelqu'un peut ajouter de nouvelles fonctionnalités / comportements sans les tester, ce qui signifierait après que vos statistiques de couverture peuvent être à 100% et que vous empilez du code qui n'est pas affirmé ou vérifié.
la source