Vérification d'appel TDD Mock - est-ce un anti-modèle?

11

Je fais du TDD depuis un an maintenant, je m'en sens plutôt bien, j'adore mes suites de tests et tout. Cependant, j'ai remarqué que récemment, je faisais beaucoup de vérification des appels simulés. Par exemple, j'aurais un service qui aura un référentiel injecté - dans mon test unitaire, je passerais une maquette du référentiel et vérifierais qu'il a été appelé dans la méthode que je teste. Je vérifierais ensuite si les résultats retournés sont corrects (dans un autre test). Cela "se sent" vraiment mal, car mes tests unitaires sont désormais très couplés aux détails de mise en œuvre. J'ai entendu dire que vous devriez tester le "comportement", mais dans beaucoup de situations c'est ... emm - pas possible? Si tu as unvoidpar exemple, vous testez généralement les effets secondaires. Je veux dire qu'il est facile d'aller de l'avant et de montrer quelques codes-kata simples où cela peut être démontré, mais à mon humble avis, cela ne reflète pas très bien les programmes du monde réel que nous écrivons. Est-ce que je fais mal? Ce type de test est-il une sorte d'anti-modèle? J'apprécierais votre avis à ce sujet, je suis toujours un peu novice en ce qui concerne TDD.

Dimitar Dimitrov
la source
2
Réponse courte: oui. Il y a déjà des questions très intéressantes sur ce sujet quelque part ici. Vos tests unitaires ne doivent pas être fragiles et dépendent fortement de votre implémentation. C'est pourquoi les tests de niveau supérieur sont destinés (intégration, etc.). Ici: programmers.stackexchange.com/questions/198453/…
Kemoda
@Kemoda Je vous serais reconnaissant si vous pouvez me lier à une discussion ou à d'autres documents à ce sujet, j'aimerais beaucoup améliorer mes techniques.
Dimitar Dimitrov
1
vous l'avez par exemple programmers.stackexchange.com/questions/198453/… je trouverai d'autres liens plus tard
Kemoda

Réponses:

8

Eh bien, vous devriez essayer de tester les entrées et les sorties. Vous devez vérifier le comportement visible de l'extérieur. Les «promesses» ou «contrats» que votre classe fait.

En même temps, il n'y a parfois pas de meilleure façon de tester une méthode que de faire ce que vous avez dit.

Je pense que cela rend votre test plus fragile, vous devriez donc éviter les tests qui s'appuient sur les détails d'implémentation si vous le pouvez, mais ce n'est pas un accord tout ou rien. C'est OK parfois, la pire chose qui arrive est de changer l'implémentation et de mettre à jour le test.

M. Dudley
la source
2

Le but d'un test est de restreindre les implémentations productives possibles. Assurez-vous de ne mettre que des restrictions sur l'implémentation dont vous avez réellement besoin. Généralement, c'est ce que votre programme doit faire, et non comment il le fait.

Donc, par exemple, si votre service ajoute quelque chose au référentiel, vous devez tester que la nouvelle entrée est contenue dans le référentiel par la suite, et non que l'action d'ajout est déclenchée.

Pour que cela fonctionne, vous devez pouvoir utiliser l'implémentation du référentiel (testé ailleurs) dans le test du service. J'ai trouvé que l'utilisation de l'implémentation réelle d'un collaborateur est généralement une bonne approche - car c'est vraiment la meilleure implémentation.


"Alors, que se passe-t-il si l'utilisation des implémentations réelles dans le test est coûteuse (par exemple parce qu'elles nécessitent des ressources compliquées à configurer)? J'ai besoin d'utiliser des simulations dans ce cas, non?"

Dans tous les cas, vous voudrez probablement un test d'intégration qui teste que les implémentations réelles fonctionnent ensemble. Assurez-vous que ce test d'intégration est suffisant pour tester votre service. Ou en d'autres termes: si un service connecte un grand nombre de collaborateurs (et est donc potentiellement difficile à tester), assurez-vous qu'il ne contient aucune logique. Si c'est le cas, et que vous auriez besoin de plusieurs tests (d'intégration), vous devez changer la structure de votre code, par exemple en isolant la logique et en la rendant ainsi plus testable.

Dans ce cas, l'utilisation de simulateurs soulage la douleur de tester un morceau de logique mal isolé et cache donc un problème architectural . N'utilisez donc pas de maquette pour tester du code mal structuré, mais corrigez plutôt la structure.

oberlies
la source
1
Je vois ce que tu dis. Ce sujet est un peu déroutant quant à "combien de tests sont trop de tests", disons que j'ai un "service agrégé" qui est fondamentalement une façade et juste "colle" ensemble un tas d'autres services / référentiels / composants quel type de tests écrivez-vous pour cela? Je ne pense qu'à la vérification des appels. J'espère que j'ai du sens.
Dimitar Dimitrov
2

Mes pensées re: «services agrégés».

La vérification des appels le fera, mais n'apportera pas beaucoup de valeur. Vous vérifiez simplement votre câblage.

Il existe 3 autres moyens non exclusifs:

  1. Étendez les tests que vous avez pour chaque service individuel afin qu'il vérifie le comportement de niveau supérieur. Par exemple, si vous atteignez une base de données en mémoire dans vos tests unitaires du service, augmentez-le d'un niveau afin de tester le service par rapport à une base de données réelle. La couche de service est plus haut dans l'arborescence d'abstraction, tout comme votre test.

  2. Utilisez la génération de code pour créer le service directement à partir des services agrégés.

  3. Utilisez une sorte de réflexion ou un langage dynamique pour faire la même chose. Par exemple, en Java, il peut être possible d'utiliser une interface groovy, qui transmet l'appel directement.

Il existe probablement d'autres façons de le faire, mais le simple fait de vérifier le câblage a un retour sur investissement très faible et vous connectera en dur à cette implémentation.

andrew oxenburgh
la source