Je lisais des articles de blog ce matin et je suis tombé sur celui-ci :
Si la seule classe qui implémente l'interface client est CustomerImpl, vous n'avez pas vraiment de polymorphisme et de substituabilité car il n'y a rien en pratique à substituer au moment de l'exécution. C'est une fausse généralité.
Cela a du sens pour moi, car l'implémentation d'une interface ajoute de la complexité et, s'il n'y a qu'une seule implémentation, on pourrait dire qu'elle ajoute une complexité inutile. L'écriture d'un code plus abstrait qu'il ne devrait l'être est souvent considérée comme une odeur de code appelée «généralité spéculative» (également mentionnée dans la publication).
Mais, si je suis TDD, je ne peux pas (facilement) créer des doubles de test sans cette généralité spéculative, que ce soit sous la forme d'une implémentation d'interface ou de notre autre option polymorphe, rendant la classe héritable et ses méthodes virtuelles.
Alors, comment pouvons-nous concilier ce compromis? Vaut-il la peine d'être spéculativement général pour faciliter les tests / TDD? Si vous utilisez des doubles de test, ceux-ci comptent-ils comme deuxièmes implémentations et ne rendent donc pas la généralité spéculative? Devriez-vous envisager un cadre de simulation plus lourd qui permet de se moquer de collaborateurs concrets (par exemple, Moles contre Moq dans le monde C #)? Ou, devriez-vous tester avec les classes concrètes et écrire ce qui pourrait être considéré comme des tests "d'intégration" jusqu'à ce que votre conception nécessite naturellement un polymorphisme?
Je suis curieux de lire les opinions des autres sur ce sujet - merci d'avance pour vos opinions.
la source
Réponses:
Je suis allé lire le billet de blog et je suis d'accord avec beaucoup de ce que l'auteur a dit. Cependant, si vous écrivez votre code à l'aide d'interfaces à des fins de test unitaire, je dirais que l'implémentation simulée de l'interface est votre deuxième implémentation. Je dirais que cela n'ajoute vraiment pas beaucoup de complexité à votre code, surtout si le compromis de ne pas le faire a pour conséquence que vos classes sont étroitement couplées et difficiles à tester.
la source
Stream
classe, mais il n'y a pas de couplage étroit.Tester le code en général n'est pas facile. Si c'était le cas, nous l'aurions fait il y a longtemps et nous ne nous en serions pas beaucoup occupés au cours des 10 à 15 dernières années. L'une des plus grandes difficultés a toujours été de déterminer comment tester du code écrit de manière cohérente, bien factorisée et testable sans rompre l'encapsulation. Le directeur de BDD suggère que nous nous concentrions presque entièrement sur le comportement et, à certains égards, semble suggérer que vous n'avez pas vraiment besoin de vous soucier des détails intérieurs dans une large mesure, mais cela peut souvent rendre les choses assez difficiles à tester s'il y en a. de nombreuses méthodes privées qui "trucent" de manière très cachée, car cela peut augmenter la complexité globale de votre test pour traiter tous les résultats possibles à un niveau plus public.
La moquerie peut aider dans une certaine mesure, mais là encore, elle est plutôt orientée vers l'extérieur. L'injection de dépendances peut également fonctionner très bien, encore une fois avec des simulations ou des tests doubles, mais cela peut également nécessiter que vous exposiez des éléments soit via une interface, soit directement, que vous auriez sinon préféré préférer rester masqués - cela est particulièrement vrai si vous souhaitez avoir un bon niveau de sécurité paranoïaque sur certaines classes de votre système.
Pour moi, le jury n'est toujours pas sur la question de savoir si vous devez concevoir vos cours pour qu'ils soient plus facilement testables. Cela peut créer des problèmes si vous devez fournir de nouveaux tests tout en conservant le code hérité. J'accepte que vous devriez pouvoir tester absolument tout dans un système, mais je n'aime pas l'idée d'exposer - même indirectement - les internes privés d'une classe, juste pour que je puisse écrire un test pour eux.
Pour moi, la solution a toujours été d'adopter une approche assez pragmatique et de combiner un certain nombre de techniques adaptées à chaque situation spécifique. J'utilise beaucoup de doublons de test hérités pour exposer les propriétés internes et les comportements de mes tests. Je me moque de tout ce qui peut être attaché à mes classes, et là où cela ne compromettra pas la sécurité de mes classes, je fournirai un moyen de remplacer ou d'injecter des comportements à des fins de test. J'envisagerai même de proposer une interface plus événementielle si cela permet d'améliorer la capacité de tester le code
Là où je trouve un code "non testable" , je cherche à voir si je peux refactoriser pour rendre les choses plus testables. Lorsque vous avez beaucoup de code privé faisant des trucs cachés dans les coulisses, vous trouverez souvent de nouvelles classes en attente d'être éclatées. Ces classes peuvent être utilisées en interne, mais peuvent souvent être testées de manière indépendante avec moins de comportements privés, et par la suite souvent moins de couches d'accès et de complexité. Une chose que je prends soin d'éviter, cependant, est d'écrire du code de production avec un code de test intégré. Il peut être tentant de créer des " cosses de test " qui aboutissent à inclure des horreurs telles que
if testing then ...
, ce qui indique un problème de test pas complètement déconstruit et incomplètement résolu.Vous pourriez trouver utile de lire le livre xUnit Test Patterns de Gerard Meszaros , qui couvre tout ce genre de choses avec beaucoup plus de détails que je ne peux en parler ici. Je ne fais probablement pas tout ce qu'il suggère, mais cela aide à clarifier certaines des situations de test les plus difficiles à gérer. À la fin de la journée, vous voulez être en mesure de satisfaire vos exigences de test tout en appliquant vos conceptions préférées, et il est utile d'avoir une meilleure compréhension de toutes les options afin de mieux décider où vous devrez peut-être faire des compromis.
la source
Le langage que vous utilisez a-t-il un moyen de "se moquer" d'un objet à tester? Si c'est le cas, ces interfaces ennuyeuses peuvent disparaître.
Sur une note différente, il peut y avoir des raisons d'avoir une SimpleInterface et un seul ComplexThing qui l'implémente. Il peut y avoir des éléments du ComplexThing que vous ne souhaitez pas que l'utilisateur de SimpleInterface puisse accéder. Ce n'est pas toujours à cause d'un codeur OO-ish trop exubérant.
Je vais m'éloigner maintenant et laisser tout le monde sauter sur le fait que le code qui fait cela "sent mauvais" pour eux.
la source
Je répondrai en deux parties:
Vous n'avez pas besoin d'interfaces si vous êtes uniquement intéressé par les tests. J'utilise des frameworks moqueurs à cet effet (en Java: Mockito ou easymock). Je pense que le code que vous concevez ne doit pas être modifié à des fins de test. Écrire du code testable équivaut à écrire du code modulaire, j'ai donc tendance à écrire du code modulaire (testable) et à tester uniquement les interfaces publiques de code.
J'ai travaillé dans un grand projet Java et je suis profondément convaincu que l'utilisation d'interfaces en lecture seule (uniquement les getters) est la voie à suivre (veuillez noter que je suis un grand fan de l'immuabilité). La classe d'implémentation peut avoir des setters, mais c'est un détail d'implémentation qui ne doit pas être exposé aux couches externes. Dans une autre perspective, je préfère la composition à l'héritage (modularité, tu te souviens?), Donc les interfaces aident aussi ici. Je suis prêt à payer le prix d'une généralité spéculative plutôt que de me tirer une balle dans le pied.
la source
J'ai vu de nombreux avantages depuis que j'ai commencé à programmer davantage sur une interface au-delà du polymorphisme.
Beaucoup de gens conviendront que des classes plus nombreuses et plus petites valent mieux que des classes moins nombreuses et plus grandes. Vous n'avez pas à vous concentrer sur autant en même temps et chaque classe a un objectif bien défini. D'autres peuvent dire que vous ajoutez à la complexité en ayant plus de classes.
Il est bon d'utiliser des outils pour améliorer la productivité, mais je pense que s'appuyer uniquement sur des frameworks Mock et autres au lieu de construire la testibilité et la modularité directement dans le code se traduira par un code de qualité inférieure à long terme.
Dans l'ensemble, je crois que cela m'a aidé à écrire du code de meilleure qualité et les avantages l'emportent de loin sur toutes les conséquences.
la source