Je viens juste d'apprendre TDD. D'après ce que j'ai compris, les méthodes privées sont indestructibles et ne devraient pas vous inquiéter, car l'API publique fournira suffisamment d'informations pour vérifier l'intégrité d'un objet.
J'ai compris la POO depuis un moment. Je crois comprendre que les méthodes privées rendent les objets plus encapsulés, donc plus résistants au changement et aux erreurs. Elles devraient donc être utilisées par défaut et seules les méthodes importantes pour les clients devraient être rendues publiques.
Eh bien, il est possible pour moi de créer un objet qui n’a que des méthodes privées et qui interagit avec d’autres objets en écoutant leurs événements. Ce serait très encapsulé, mais complètement indestructible.
En outre, il est considéré comme une mauvaise pratique d’ajouter des méthodes à des fins de test.
Est-ce que cela signifie que TDD est en contradiction avec l'encapsulation? Quel est le bon équilibre? Je suis enclin à rendre publiques la plupart ou la totalité de mes méthodes ...
la source
Réponses:
Préférez les tests à l'interface plutôt que les tests sur l'implémentation.
Cela dépend de votre environnement de développement, voir ci-dessous.
C'est vrai, TDD se concentre sur le test de l'interface.
Les méthodes privées sont des détails d'implémentation qui peuvent changer au cours d'un cycle de re-factorisation. Il devrait être possible de re-factoriser sans changer l'interface ou le comportement de la boîte noire . En fait, cela fait partie des avantages de TDD, la facilité avec laquelle vous pouvez générer la confiance que les modifications internes à une classe n’affecteront pas les utilisateurs de cette classe.
Même si la classe n'a pas de méthodes publiques, ses gestionnaires d'événements sont son interface publique , et c'est contre cette interface publique que vous pouvez tester.
Étant donné que les événements constituent l'interface, vous devez générer ces événements pour tester cet objet.
Examinez l'utilisation de faux objets comme colle pour votre système de test. Il devrait être possible de créer un objet fictif simple qui génère un événement et enregistre le changement d'état résultant (possible avec un autre objet fictif de récepteur).
Absolument, vous devriez être très prudent d'exposer votre état interne.
Absolument pas.
TDD ne devrait pas changer l'implémentation de vos classes autrement que pour les simplifier (en appliquant YAGNI à partir d'un point précédent).
La meilleure pratique avec TDD est identique à la meilleure pratique sans TDD, vous devez simplement savoir pourquoi plus tôt, car vous utilisez l'interface au fur et à mesure que vous la développez.
Ce serait plutôt jeter le bébé avec l'eau du bain.
Vous ne devriez pas avoir besoin de rendre toutes les méthodes publiques pour pouvoir développer de manière TDD. Voir mes notes ci-dessous pour voir si vos méthodes privées sont vraiment indestructibles.
Un regard plus détaillé sur le test des méthodes privées
Si vous devez absolument tester certains comportements privés d'une classe, en fonction de la langue / de l'environnement, vous pouvez avoir trois options:
Évidemment, la 3ème option est de loin la meilleure.
1) Mettez les tests dans la classe que vous souhaitez tester (pas idéal)
Stocker des cas de test dans le même fichier classe / source que le code de production testé est l’option la plus simple. Mais sans beaucoup de directives ou d'annotations pré-processeurs, votre code de test gonfle inutilement votre code de production et, en fonction de la structure de votre code, vous risquez d'exposer accidentellement une implémentation interne à ses utilisateurs.
2) Exposer les méthodes privées que vous voulez tester en tant que méthodes publiques (vraiment pas une bonne idée)
Comme suggéré, cette pratique est très mauvaise, détruit l’encapsulation et exposera l’implémentation interne aux utilisateurs du code.
3) Utiliser un meilleur environnement de test (meilleure option, si disponible)
Dans le monde Eclipse, 3. peut être obtenu en utilisant des fragments . Dans le monde C #, nous pourrions utiliser des classes partielles . Les autres langues / environnements ont souvent des fonctionnalités similaires, il vous suffit de les trouver.
En supposant aveuglément que 1 ou 2 soient les seules options possibles, le logiciel de production serait surchargé de code de test ou d'interfaces de classe désagréables lavant leur linge sale en public. * 8 ')
la source
Bien sûr, vous pouvez avoir des méthodes privées, et bien sûr, vous pouvez les tester.
Soit il existe un moyen d’exécuter la méthode privée, auquel cas vous pouvez la tester de cette façon, ou il n’existe aucun moyen d’exécuter le processus privé, auquel cas: pourquoi diable essayez-vous de la tester? supprimer la fichue chose!
Dans votre exemple:
Pourquoi cela serait-il impossible Si la méthode est invoquée en réaction à un événement, il suffit que le test alimente l'objet en événement approprié.
Il ne s'agit pas de ne pas avoir de méthodes privées, mais de ne pas casser l'encapsulation. Vous pouvez avoir des méthodes privées mais vous devez les tester via l'API publique. Si l'API publique est basée sur des événements, utilisez-les.
Dans le cas le plus courant des méthodes d'assistance privée, elles peuvent être testées via les méthodes publiques qui les appellent. En particulier, étant donné que vous n'êtes autorisé à écrire du code que pour réussir un test et que vos tests testent l'API publique, tous les nouveaux codes que vous écrivez seront généralement publics. Les méthodes privées apparaissent uniquement à la suite d'un refactoring de méthode d'extraction , lorsqu'elles sont extraites d'une méthode publique déjà existante. Mais dans ce cas, le test d'origine qui teste la méthode publique couvre toujours la méthode privée également, car la méthode publique appelle la méthode privée.
Ainsi, les méthodes privées n'apparaissent généralement que lorsqu'elles sont extraites de méthodes publiques déjà testées et sont donc également testées.
la source
internal
méthodes ou les méthodes publiques dans lesinternal
classes doivent être testées directement assez souvent. Heureusement, .net prend en charge laInternalsVisibleToAttribute
, mais sans elle, tester ces méthodes serait un PITA.Lorsque vous créez une nouvelle classe dans votre code, vous le faites pour répondre à certaines exigences. Les exigences indiquent ce que le code doit faire, pas comment . Cela permet de comprendre facilement pourquoi la plupart des tests sont effectués au niveau des méthodes publiques.
Par le biais de tests, nous vérifions que le code fait ce qu'il est censé faire , génère les exceptions appropriées, le cas échéant, etc. La manière dont le code est implémenté par le développeur ne nous importe pas. Même si nous ne nous soucions pas de l'implémentation, c'est-à-dire de la manière dont le code agit, il est logique d'éviter de tester des méthodes privées.
En ce qui concerne les classes de test qui n'ont aucune méthode publique et n'interagissent avec le monde extérieur que par le biais d'événements, vous pouvez également le tester en envoyant, via des tests, les événements et en écoutant la réponse. Par exemple, si une classe doit enregistrer un fichier journal chaque fois qu'elle reçoit un événement, le test unitaire l'enverra et vérifiera que le fichier journal est écrit.
Dernier point mais non le moindre, dans certains cas, il est parfaitement valide de tester des méthodes privées. C'est pourquoi, par exemple, dans .NET, vous pouvez tester non seulement les classes publiques, mais également les classes privées, même si la solution n'est pas aussi simple que pour les méthodes publiques.
la source
Je ne suis pas d'accord avec cette affirmation, ou je dirais que vous ne testez pas les méthodes privées directement . Une méthode publique peut appeler différentes méthodes privées. Peut-être que l'auteur voulait avoir de "petites" méthodes et extraire une partie du code dans une méthode privée intelligemment nommée.
Quelle que soit la manière dont la méthode publique est écrite, votre code de test doit couvrir tous les chemins. Si, après vos tests, vous découvrez qu'une des déclarations de branche (if / switch) d'une méthode privée n'a jamais été couverte dans vos tests, vous avez un problème. Soit vous avez manqué un cas et la mise en œuvre est correcte OU la mise en œuvre est incorrecte et cette branche n'aurait jamais dû exister.
C'est pourquoi j'utilise beaucoup Cobertura et NCover pour m'assurer que mon test de méthode publique couvre également les méthodes privées. N'hésitez pas à écrire de bons objets OO avec des méthodes privées et ne laissez pas TDD / Testing vous gêner dans ce domaine.
la source
Votre exemple est toujours parfaitement vérifiable tant que vous utilisez Dependency Injection pour fournir les instances avec lesquelles votre CUT interagit. Vous pouvez ensuite utiliser une maquette, générer les événements qui vous intéressent, puis observer si CUT effectue ou non les actions correctes sur ses dépendances.
D'autre part, si vous avez un langage avec un bon support aux événements, vous pouvez prendre un chemin légèrement différent. Je n'aime pas que les objets s'abonnent aux événements eux-mêmes, mais la fabrique qui crée l'objet connecte les événements aux méthodes publiques de l'objet. Il est plus facile de tester et de rendre visible de l’extérieur quels types d’événements doivent être testés pour CUT.
la source
Vous ne devriez pas avoir besoin d'abandonner en utilisant des méthodes privées. Il est parfaitement raisonnable de les utiliser, mais d’un point de vue test, il est plus difficile de tester directement sans casser l’encapsulation ni ajouter de code spécifique au test dans vos classes. L'astuce consiste à réduire au minimum les éléments qui, à votre connaissance, vont faire mal à votre intestin, car vous avez l'impression de salir votre code.
Ce sont les choses que je garde à l’esprit pour essayer d’atteindre un équilibre viable.
Pensez latéralement. Gardez vos classes et vos méthodes plus petites et utilisez beaucoup de composition. Cela ressemble à plus de travail, mais à la fin vous obtiendrez plus d'éléments individuellement testables, vos tests seront plus simples, vous aurez plus d'options pour utiliser de simples simulacres à la place d'objets réels, volumineux et complexes, espérons-le bien. code factorisé et faiblement couplé, et plus important encore, vous aurez plus d'options. Garder les choses petites a tendance à vous faire gagner du temps à la fin, car vous réduisez le nombre de choses que vous devez contrôler individuellement pour chaque classe, et vous avez tendance à réduire naturellement le code spaghetti qui peut parfois arriver quand une classe devient grande et a beaucoup comportement de code interdépendant en interne.
la source
Comment cet objet réagit-il à ces événements? Vraisemblablement, il doit invoquer des méthodes sur d'autres objets. Vous pouvez le tester en vérifiant si ces méthodes sont appelées. Faites-le appeler un objet factice et vous pourrez alors facilement affirmer qu'il fait ce que vous attendez.
Le problème est que nous voulons seulement tester l'interaction de l'objet avec d'autres objets. Nous ne nous soucions pas de ce qui se passe à l'intérieur d'un objet. Donc non, vous ne devriez plus avoir de méthodes publiques alors auparavant.
la source
J'ai également lutté avec le même problème. Vraiment, le moyen de le contourner est le suivant: comment voulez-vous que le reste de votre programme se connecte à cette classe? Testez votre classe en conséquence. Cela vous obligera à concevoir votre classe en fonction de l’interface entre le reste du programme et à encourager l’encapsulation et la bonne conception de votre classe.
la source
À la place du modificateur par défaut à usage privé. Ensuite, vous pouvez tester ces méthodes individuellement, pas seulement avec des méthodes publiques. Cela nécessite que vos tests aient la même structure de package que votre code principal.
la source
internal
en .net.Quelques méthodes privées ne sont généralement pas un problème. Vous venez de les tester via l'API publique comme si le code était intégré dans vos méthodes publiques. Un excès de méthodes privées peut être un signe de faible cohésion. Votre classe devrait avoir une responsabilité cohérente, et souvent les gens font des méthodes privées pour donner l’apparence de la cohésion là où il n’en existe pas vraiment.
Par exemple, vous pouvez avoir un gestionnaire d'événements qui effectue de nombreux appels de base de données en réponse à ces événements. Comme il est évidemment déconseillé d’instancier un gestionnaire d’événements pour effectuer des appels de base de données, il est tentant de rendre toutes les méthodes privées relatives aux appels liés à la base de données, alors qu’elles doivent être extraites dans une classe distincte.
la source
TDD n'est pas en contradiction avec l'encapsulation. Prenons l'exemple le plus simple d'une méthode ou d'une propriété de lecture, en fonction de la langue de votre choix. Supposons que j'ai un objet Client et que je souhaite qu'il ait un champ Id. Le premier test que je vais écrire est un test qui dit quelque chose comme "customer_id_initializes_to_zero". Je définis le getter pour qu'il lève une exception non implémentée et que le test échoue. Ensuite, la chose la plus simple que je puisse faire pour réussir ce test est de renvoyer le getter à zéro.
À partir de là, je passe à d’autres tests, qui impliquent vraisemblablement que l’ID client est un champ fonctionnel réel. À un moment donné, je dois probablement créer un champ privé que la classe de clients utilise pour garder une trace de ce qui doit être retourné par le getter. Comment puis-je garder une trace de cela? Est-ce un simple support int? Est-ce que je garde une chaîne et la convertis ensuite en int? Est-ce que je garde une trace de 20 ints et une moyenne? Le monde extérieur ne s'en soucie pas - et vos tests TDD ne s'en soucient pas. C'est un détail encapsulé .
Je pense que cela n’est pas toujours immédiatement évident lors du démarrage de TDD - vous ne testez pas ce que les méthodes font en interne - vous testez des préoccupations moins détaillées de la classe. Donc, vous ne cherchez pas à tester que cette méthode
DoSomethingToFoo()
instancie une barre, appelle une méthode dessus, ajoute deux à l'une de ses propriétés, etc. Vous testez qu'après avoir modifié l'état de votre objet, un accesseur d'état a changé (ou pas). C'est le schéma général de vos tests: "quand je fais du X dans ma classe sous test, je peux par la suite observer Y". La façon dont cela aboutit à Y n’est pas une préoccupation pour les tests, c’est ce qui est encapsulé et c’est pourquoi TDD n’est pas en contradiction avec l’encapsulation.la source
Évitez d'utiliser? Non .
Évitez de commencer avec ? Oui.
Je remarque que vous n'avez pas demandé s'il était acceptable d'avoir des classes abstraites avec TDD; Si vous comprenez comment les classes abstraites apparaissent lors de la définition de données, le même principe s'applique également aux méthodes privées.
Vous ne pouvez pas tester directement des méthodes dans des classes abstraites, tout comme vous ne pouvez pas directement tester des méthodes privées, mais c'est pourquoi vous ne commencez pas avec des classes abstraites et des méthodes privées; vous commencez avec des classes concrètes et des API publiques, puis vous refactorisez les fonctionnalités communes au fur et à mesure.
la source