J'ai une classe qui est refactorisée en 1 classe principale et 2 classes plus petites. Les classes principales utilisent la base de données (comme beaucoup de mes classes) et envoient un e-mail. La classe principale a donc un IPersonRepository
et un IEmailRepository
injecté qui à son tour envoie aux 2 classes plus petites.
Maintenant, je veux tester unitaire la classe principale, et j'ai appris à ne pas uniter le fonctionnement interne de la classe, parce que nous devrions pouvoir changer le fonctionnement interne sans casser les tests unitaires.
Mais comme la classe utilise le IPersonRepository
et un IEmailRepository
, JE DOIS spécifier (mock / dummy) les résultats de certaines méthodes pour le IPersonRepository
. La classe principale calcule certaines données sur la base des données existantes et les renvoie. Si je veux tester cela, je ne vois pas comment je peux écrire un test sans spécifier que le IPersonRepository.GetSavingsByCustomerId
retourne x. Mais ensuite, mon test unitaire «connaît» le fonctionnement interne, car il «sait» quelles méthodes se moquer et lesquelles ne le sont pas.
Comment puis-je tester une classe qui a injecté des dépendances, sans que le test connaisse les internes?
Contexte:
D'après mon expérience, de nombreux tests comme celui-ci créent des simulations pour les référentiels, puis fournissent les bonnes données pour les simulations ou testent si une méthode spécifique a été appelée pendant l'exécution. Quoi qu'il en soit, le test connaît les éléments internes.
Maintenant, j'ai vu une présentation sur la théorie (que j'ai entendue auparavant) selon laquelle le test ne devrait pas connaître l'implémentation. D'abord parce que vous ne testez pas comment cela fonctionne, mais aussi parce que lorsque vous modifiez maintenant l'implémentation, tous les tests unitaires échouent parce qu'ils «connaissent» l'implémentation. Bien que j'aime que le concept des tests ne soit pas au courant de l'implémentation, je ne sais pas comment l'accomplir.
la source
IPersonRepository
objet, cette interface et toutes les méthodes qu'elle décrit ne sont plus "internes", donc ce n'est pas vraiment un problème de test. Votre vraie question devrait être "comment puis-je refactoriser des classes en unités plus petites sans trop exposer en public". La réponse est "garder ces interfaces allégées" (en respectant le principe de séparation des interfaces, par exemple). C'est le point 2 à mon humble avis dans la réponse de @ DavidArno (je suppose qu'il n'est pas nécessaire que je répète cela dans une autre réponse).Réponses:
Vous avez raison de dire qu'il s'agit d'une violation du principe «ne testez pas les composants internes» et qu'il est courant que les gens l'ignorent.
Il existe deux solutions que vous pouvez adopter pour contourner cette violation:
1) Fournissez une maquette complète de
IPersonRepository
. Votre approche actuellement décrite consiste à coupler la simulation au fonctionnement interne de la méthode testée en se moquant uniquement des méthodes qu'elle appellera. Si vous fournissez des maquettes pour toutes les méthodes deIPersonRepository
, vous supprimez ce couplage. Le fonctionnement interne peut changer sans affecter la maquette, ce qui rend le test moins fragile.Cette approche a l'avantage de garder le mécanisme DI simple, mais elle peut créer beaucoup de travail si votre interface définit de nombreuses méthodes.
2) Ne pas injecter
IPersonRepository
, injecter laGetSavingsByCustomerId
méthode ou injecter une valeur d'épargne. Le problème avec l'injection d'implémentations d'interface entières est que vous injectez ensuite ("dites, ne demandez pas") un système "demandez, ne dites pas", mélangeant les deux approches. Si vous adoptez une approche "DI pur", la méthode doit être indiquée (dire) la méthode exacte à appeler si elle veut une valeur d'épargne, plutôt que de se voir attribuer un objet (qu'elle doit ensuite demander efficacement la méthode à appeler).L'avantage de cette approche est qu'elle évite le besoin de simulations (au-delà des méthodes de test que vous injectez dans la méthode testée). L'inconvénient est que cela peut entraîner la modification des signatures de méthode en réponse aux exigences de changement de méthode.
Les deux approches ont leurs avantages et leurs inconvénients, alors choisissez celle qui convient le mieux à vos besoins.
la source
Mon approche consiste à créer des versions «fictives» des référentiels qui lisent à partir de fichiers simples contenant les données nécessaires.
Cela signifie que le test individuel ne «connaît» pas la configuration de la maquette, bien que le projet de test global fasse évidemment référence à la maquette et contienne les fichiers de configuration, etc.
Cela évite la configuration complexe des objets fantômes requis par les frameworks de simulation et vous permet d'utiliser les objets «simulés» dans des instances réelles de votre application pour les tests d'interface utilisateur et similaires.
Étant donné que la «maquette» est entièrement implémentée, plutôt que d'être spécifiquement configurée pour votre scénario de test, un changement d'implémentation, par exemple, permet de dire que GetSavingsForCustomer devrait désormais également supprimer le client. Ne cassera pas les tests (à moins bien sûr que cela casse vraiment les tests), vous n'avez qu'à mettre à jour votre implémentation simulée unique et tous vos tests s'exécuteront sans changer leur configuration
la source
Les tests unitaires sont généralement des tests en boîte blanche (vous avez accès au vrai code). Par conséquent, il est correct de connaître les internes dans une certaine mesure, mais pour les débutants, il est plus facile de ne pas le faire, car vous ne devriez pas tester le comportement interne (tel que "appeler la méthode a en premier, puis b puis a nouveau").
Injecter une maquette qui fournit des données est correct, car votre classe (unité) dépend de ces données externes (ou fournisseur de données). Cependant, vous devez vérifier les résultats (pas le moyen de les atteindre)! Par exemple, vous fournissez une instance Personne et vérifiez qu'un e-mail a été envoyé à la bonne adresse e-mail. Par exemple, en fournissant une maquette pour l'e-mail également, cette maquette ne fait que stocker l'adresse e-mail du destinataire pour un accès ultérieur par votre test. -code. (Je pense que Martin Fowler les appelle Stubs plutôt que Mocks, cependant)
la source