8
votes

Comment puis-je empêcher mes tests de l'unité de nécessiter des connaissances sur les internes de la mise en œuvre lors de l'utilisation de faux objets?

Je suis toujours dans les étapes d'apprentissage concernant l'unité-test et en particulier en ce qui concerne la moqueur (j'utilise le PASCALMOCK et Dunit frameworks). Une chose que je suis désormais trébuchée sur le fait que je ne pouvais pas trouver un moyen de trouver des détails de mise en œuvre de codage acharné de la classe / interface testée dans mon test de l'unité et qui se sent mal ...

Par exemple: je veux tester une classe qui implémente une interface très simple pour la lecture et l'écriture des paramètres d'application (fondamentalement des paires de noms / valeur). L'interface présentée au consommateur est complètement agnostique à l'endroit où et comment les valeurs sont réellement stockées (par exemple, le registre, INI-File, XML, la base de données, etc.). Naturellement, la couche d'accès est mise en œuvre par une classe différente qui est injectée dans la classe testée sur la construction. J'ai créé un faux objet pour cette couche d'accès et je suis maintenant capable de tester pleinement la classe d'implémentation de l'interface sans lire ou écrire quoi que ce soit à aucun registre / ini-fichier / autre.

Toutefois, afin de garantir que la maquette se comporte exactement comme la chose réelle lorsqu'elle est accessible par la classe testée, mes tests de l'unité doivent configurer l'objet simulé en définissant très explicitement les appels de méthode attendus et les valeurs de retour attendues par la classe testée. . Cela signifie que si je devais jamais avoir à modifier l'interface de la couche d'accès ou à la manière dont la classe testée utilise cette couche, je devrai également modifier les tests d'unité de la classe qui utilise cette interface même si la < Strong> Interface de la classe que je teste réellement n'a pas changé du tout. Est-ce quelque chose que je vais juste devoir vivre avec des simulacres ou y a-t-il un meilleur moyen de concevoir les dépendances de classe qui éviteraient cela?


0 commentaires

3 Réponses :


7
votes

Pour assurer que le maquette se comporte exactement comme la vraie chose lorsqu'il est accessible par la classe testée, mes tests d'unité doivent configurer l'objet simulé en définissant très explicitement les appels de méthode attendus et les valeurs de retour attendues par la classe testée.

correct.

Modifications de l'interface de la couche d'accès ou de la manière dont la classe testée utilise cette couche, je devrai également modifier les tests d'unité

correct.

Même si l'interface de la classe que je teste réellement n'a pas changé du tout.

"tester réellement"? Vous voulez dire la classe d'interface exposée? C'est très bien.

La manière dont la classe "Testée" (interface) utilise la couche d'accès signifie que vous avez modifié l'interface interne vers la couche d'accès. Les changements d'interface (même les internes) nécessitent des changements de test et peuvent conduire à une rupture si vous avez fait quelque chose de mal.

rien de mal à cela. En effet, tout le point est que tout changement de la couche d'accès doit nécessite des modifications aux moqueurs pour assurer que le changement "fonctionne".

Les tests ne sont pas censés être "robustes". C'est censé être fragile. Si vous modifiez un changement qui modifie le comportement interne, les choses peuvent pause. Si vos tests étaient trop robustes, ils ne testaient rien - ils ne feraient que travailler. Et c'est faux.

Les tests ne doivent fonctionner que pour la raison exacte exacte.


10 commentaires

Merci, cela ressemble à une très bonne ligne de raisonnement. Cependant, j'avais eu l'impression que les tests unitaires sont censés traiter leurs sujets (c.-à-d. Les classes et méthodes testées) plus comme des boîtes noires, se concentrer pleinement sur ce que ils font plutôt que / I> Ils le font. Pourtant, dans ce cas, je suis essentiellement forcé de dupliquer la quasi-totalité de la mise en œuvre des méthodes testées sous la forme d'attentes simulées ... n'est-ce pas aussi contraire au principe de la boîte à noir? Ou est-ce que je l'ai complètement mal?


C'est censé être fragile ? Je ne sais pas si je suis d'accord sur ça.


J'encourage l'OP à rechercher des informations supplémentaires sur le développement classique vs. Mockist Test axé sur les tests ici << a href = "http://codebetter.com/blogs/ian_cooper/archive/2008/02/04/classicist-vs-mockist- test-driven-dotation.aspx "rel =" nofollow noreferrer "> codebetter.com/blogs/ian_cooper/archive/2008/02/04/... >, et surtout ici << a href =" http: / /Stackoverflow.com/questions/184666/shald-i-practice-mockist-or-Classical-DDD "Titre =" Devrais-je mettre en pratique un moqueur ou un TDD classique "> Stackoverflow.com/questions/184666/... >. Ne pas contraire à ce que vous dites, mais cela donne d'autres points de vue.


"N'est-ce pas assez contraire au principe de la boîte noire". Non, vous savez savoir trop de de la manière dont la chose est mise en œuvre. Depuis que vous savez trop de la mise en œuvre, vous vous inquiétez de la duplication. Depuis que vous en savez trop, vous pouvez dupliquer lorsque vous écrivez le simulacre. Si vous avez apporté un programmeur innocent N00B, ils peuvent écrire un simulacre plus simple. Ou, ils peuvent faire ce que vous avez fait et écrivez une simule qui a une structure similaire à la classe réelle. Peu importe. Vous vous inquiétez parce que vous savez trop à propos de la moqueur et de la mise en œuvre.


@Lieven: Merci pour les liens! Je n'étais pas encore au courant de cet argument. Très intéressant lit jusqu'à présent.


@ S.Lott: Ce n'est pas exactement que je viens de "arriver à savoir trop de la manière dont la chose est mise en œuvre". Ce que je suis inquiétant, c'est plus le fait que je doit avoir à * savoir tout cela afin de mettre en place le test en premier lieu. Un noob qui ne sait pas comment la classe testée utilise la couche d'accès interne ne sera tout simplement pas capable d'écrire un test à l'aide d'une couche d'accès simulée ...


@Oliver Giesen: Le N00B a besoin d'une spécification d'interface minimale pour créer le simulacre. Ce qu'ils feraient, assez bien. Lorsque vous avez regardé leur mock, vous pourriez dire «c'est beaucoup comme la mise en œuvre réelle». Seulement vous pouvez faire le jugement, pas le N00b. Le N00B a créé la simulation, purement des spécifications d'interface. Peut-être que votre interface est «trop basse» et oblige une implémentation spécifique. Votre approche est absolument juste. Votre souci de vos moqueurs d'être trop comme la mise en œuvre est égaré.


Re: Le problème des tests fragiles: les tests n'ont pas besoin d'être fragiles. Ils peuvent être écrits génériquement pour permettre une faible flexibilité lors du test, mais d'une perspective axée sur les tests, cela dévaluerait probablement les tests eux-mêmes. Les modifications apportées au code testé doivent casser des tests et constitue l'un des principes qui sous-tendent la méthodologie de refactorisation du son, mais cela ne signifie pas toujours que la stratégie de test correcte a été appliquée. Si vous écrivez des tests bien facturés, ils seront cassants parfois (par exemple: interfaces), flexibles d'autres fois (par exemple: certains comportements), de sorte que «l'argument» n'est pas vraiment aussi polarisé que cela puisse paraître.


@ S.Robins: "Les modifications apportées au code testé doivent casser des tests". D'accord. "L'argument" n'est pas vraiment aussi polarisé ". Probablement vrai. Bien que cette question puisse indiquer qu'il est polarisant pour certaines personnes. Le problème se pose lorsque nous autorisons un développeur à des tests «sur-ingénieur» pour les rendre robustes. Comme vous l'avez dit, c'est une fine ligne et je fais du lobby pour toujours descendre du côté fragile. Un test cassé a fonctionné - il a détecté un changement.


@ S.Lott: Oui, la ligne fine entre sur-ingénieur vs appropriée est un problème clé et probablement où des vues polarisées sont enracinées. Je conviens qu'un test cassé fonctionne s'il détecte le changement, mais à nouveau, cela doit être équilibré, et seule l'expérience peut vous dire où dessiner la ligne ... et même vous pourriez ne pas le faire "à droite". En fait, je pense que la dernière ligne de votre post monte le mieux (et je vous citerai probablement de cela). "Les tests ne doivent travailler que pour la raison exacte". Si nous traitons cela comme une règle d'or, alors quelle cassite ou robuste le test est discutable.



6
votes

Est-ce quelque chose que je vais juste avoir à vivre avec quand vous utilisez des moqueurs ou est là une meilleure façon de concevoir le dépendances de classe qui éviteraient Ceci?

Beaucoup de fois se moque (des cadres particulièrement sensibles comme JMock) vous obliger à rendre compte des détails qui ne concernent pas directement le comportement que vous essayez de tester, et parfois cela peut même être utile en exposant le code suspect qui est faire trop et a trop d'appels / dépendances.

Cependant, dans votre cas, si je lisais votre description correctement, cela ressemble à ce que vous n'avez vraiment pas de problème. Si vous concevez correctement le calque de lecture / écriture et avec un niveau d'abstraction approprié, vous ne devriez pas avoir à le changer.

Cela signifie que si je devrais jamais avoir apporter des modifications à l'interface de la couche d'accès ou à la manière dont La classe testée utilise cette couche i devra également changer l'unité tests pour la classe qui en interne utilise cette interface même si la interface de la classe que je suis en fait Les tests n'ont pas du tout changé.

Ce n'est pas le point d'écrire la couche d'accès abstraite pour éviter cela? En général, après le Principe ouvert / fermé , une interface de ce type ne devrait pas changer et ne devrait pas casser le contrat avec la classe qui le consomme et par extension, il ne cassera pas non plus vos tests d'unité. Maintenant, si vous modifiez l'ordre des appels de méthode ou que vous devez effectuer de nouveaux appels à la couche abstraite, alors oui, en particulier avec certains cadres, vos attentes simulées vont casser. Cela ne fait que partie du coût de l'utilisation de simulacres, et c'est parfaitement acceptable. Mais l'interface elle-même devrait, en général, rester stable.


2 commentaires

Le plus gros problème avec PASCALMOCK (par rapport à certains des autres cadres plus robustes) est que vous devez définir manuellement les moqueurs comme des classes complètes. Cela ressemble à un grand temps, mais en général, vous ne faites que cela une fois pour obtenir un ajout agréable et puissant à votre boîte à outils de test. Pour paraphraser un peu, avoir une couche d'abstraction bien définie contribue à réduire quelque peu la douleur et vous propose une solution qui vous permet d'ignorer le fonctionnement intérieur des classes qui ne sont pas testées. Le compromis est qu'il est fragile si les interfaces doivent être modifiées.


@ S.Robbins: Quels "autres cadres plus robustes" pensais-tu? J'étais sous l'impression que Pascalmock, aussi incomplète que possible, actuellement est le cadre de moqueur le plus mature pour Delphi. Ou pensez-vous des cadres non delphes?



1
votes

Juste pour mettre des noms à votre exemple,

  • RegistryBasedDictionary implémente le dictionnaire Rôle (Interface).
  • RegistryBaseddictionary a une dépendance sur le Rôle RegistryAccessor, mis en œuvre par RegistrywinapiWrapper.

    Vous êtes actuellement intéressé par des tests RegistryDdictionArdiction. Les tests unitaires injecteraient une dépendance simulée pour le rôle du registre et testeraient l'interaction attendue avec les dépendances.

    • Le truc ici pour éviter que la maintenance des tests inutiles est de " préciser précisément ce qui devrait se produire .. et plus". "(de Le livre de Goos (digne de lecture pour la simulation de TDD parfumée), de sorte que, si l'ordre de méthode de dépendance n'a pas d'importance, ne le précise pas. le test. Cela vous laisse libre de modifier l'ordre des appels dans la mise en œuvre.)
    • Concevez les rôles de telle sorte que ceux-ci ne contiennent aucune fuite des implémentations réelles - garder les rôles de mise en œuvre-agnostique .

      La seule raison de modifier les tests consécutifs à la régie, constituerait une modification du comportement de la régieBaseddicary et non dans aucune de ses dépendances. Donc, si l'interaction avec ses dépendances ou les rôles / contrats change, les tests devraient être mis à jour . C'est le prix des tests basés sur les interactions que vous devez payer, pour la flexibilité nécessaire pour tester isolément. Cependant, dans la pratique, ce n'est pas que cela se passe souvent.


0 commentaires