Devrais-je éviter les méthodes privées si j'exécute TDD?

101

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 ...

chiot
la source
9
Mauvaise pratique et réalité dans l'industrie du logiciel sont des animaux différents. Une situation idéale est le plus souvent une réalité déformée du monde des affaires. Faites ce qui a du sens et respectez-le tout au long de l'application. Je préférerais de loin avoir une mauvaise pratique en place que la saveur du mois répandue dans toute l'application.
Aaron McIver
10
"les méthodes privées ne sont pas testables"? Quelle langue? Dans certaines langues, c'est gênant. Dans d'autres langues, c'est parfaitement simple. En outre, dites-vous que le principe de conception de l'encapsulation doit toujours être mis en œuvre avec de nombreuses méthodes privées? Cela semble un peu extrême. Certaines langues n'ont pas de méthodes privées, pourtant, semblent toujours avoir des dessins bien encapsulés.
S.Lott
"Je crois comprendre que les méthodes privées rendent les objets plus encapsulés, ce qui les rend plus résistants au changement et aux erreurs. Par conséquent, elles devraient être utilisées par défaut et seules les méthodes importantes pour les clients devraient être rendues publiques." Cela me semble être le point de vue opposé à ce que TDD tente de réaliser. TDD est une méthodologie de développement qui vous amène à créer une conception simple, exploitable et ouverte aux changements. Regarder "de privé" et "ne faire de la publicité ..." est complètement inversé. Oubliez qu'il existe une méthode privée pour adopter le TDD. Plus tard, faites-les au besoin; dans le cadre de la refactorisation.
Herby
Reproduction possible de méthodes privées
gnat
Donc, @gnat pensez-vous que cela devrait être clos comme un double de la question qui découle de ma réponse à cette question? * 8 ')
Mark Booth

Réponses:

50

Préférez les tests à l'interface plutôt que les tests sur l'implémentation.

Je crois comprendre que les méthodes privées ne sont pas testables

Cela dépend de votre environnement de développement, voir ci-dessous.

[Les méthodes privées] ne devraient pas être inquiétés, car l'API publique fournira suffisamment d'informations pour vérifier l'intégrité d'un objet.

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.

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.

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).

En outre, il est considéré comme une mauvaise pratique d’ajouter des méthodes à des fins de test.

Absolument, vous devriez être très prudent d'exposer votre état interne.

Est-ce que cela signifie que TDD est en contradiction avec l'encapsulation? Quel est le bon équilibre?

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.

Je suis enclin à rendre publiques la plupart ou la totalité de mes méthodes ...

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:

  1. Placez les tests dans la classe que vous souhaitez tester.
  2. Placez les tests dans un autre fichier source / classe et exposez les méthodes privées que vous souhaitez tester en tant que méthodes publiques.
  3. Utilisez un environnement de test qui vous permet de garder le code de test et le code de production séparés, tout en autorisant l'accès du code de test aux méthodes privées du code de production.

É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 ')

  • Dans l’ensemble, il vaut bien mieux ne pas tester contre une implémentation privée.
Mark Booth
la source
5
Je ne suis pas sûr d’accepter les trois options que vous proposez. Ma préférence serait de ne tester que l'interface publique, comme vous l'avez dit précédemment, tout en veillant à ce que des méthodes privées soient exercées. L'un des avantages de cette opération est la recherche d'un code mort, ce qui est peu probable si vous forcez votre code de test à interrompre l'utilisation normale de la langue.
Magus
Vos méthodes devraient faire une chose, et un test ne devrait en aucun cas envisager la mise en œuvre. Les méthodes privées sont des détails d'implémentation. Si tester uniquement des méthodes publiques signifie que vos tests sont des tests d'intégration, vous rencontrez un problème de conception.
Magus
Qu'en est-il de rendre la méthode default / protected et de créer un test dans un projet de test avec le même package?
RichardCypher
@RichardCypher C'est exactement la même chose que 2) puisque vous modifiez ce que vos spécifications de méthode seraient idéalement pour compenser une déficience de votre environnement de test, donc une pratique encore médiocre.
Mark Booth
75

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:

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.

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.

Jörg W Mittag
la source
3
Les tests effectués avec des méthodes publiques fonctionnent bien 99% du temps. Le défi est le 1% du temps lorsque votre méthode publique unique a plusieurs centaines ou milliers de lignes de code complexe derrière et que tous les états intermédiaires sont spécifiques à la mise en œuvre. Une fois que cela devient assez complexe, essayer de résoudre tous les cas critiques de la méthode publique devient pénible au mieux. Alternativement, tester les cas extrêmes en cassant l’encapsulation et en exposant plus de méthodes comme étant privées, ou en utilisant un kludge pour faire en sorte que les tests appellent des méthodes privées aboutit directement à des cas de test fragiles en plus d’être laids.
Dan Neely
24
Les grandes méthodes privées complexes sont une odeur de code. Une implémentation si complexe qu’elle ne peut pas être décomposée utilement en composants (avec des interfaces publiques) pose un problème de testabilité qui révèle des problèmes potentiels de conception et d’architecture. Les 1% de cas où le code privé est énorme bénéficieront généralement d’un remaniement pour se décomposer et exposer.
S.Lott
13
Le code de @Dan Neely comme celui-là est assez indiscutable - et une partie de l'écriture de tests unitaires l'indique. Éliminez les états, divisez les classes, appliquez toutes les refactorisations typiques, puis écrivez des tests unitaires. Aussi avec TDD, comment êtes-vous arrivé à ce point? C'est l'un des avantages de TDD, l'écriture de code testable devient automatique.
Bill K
Au moins les internalméthodes ou les méthodes publiques dans les internalclasses doivent être testées directement assez souvent. Heureusement, .net prend en charge la InternalsVisibleToAttribute, mais sans elle, tester ces méthodes serait un PITA.
CodesInChaos
25

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.

Arseni Mourzenko
la source
4
+1 Une caractéristique importante de TDD est qu'il vous oblige à vérifier que les EXIGENCES sont remplies, plutôt que de vérifier que METHODS fait ce qu'il pense que vous faites. La question "Puis-je tester une méthode privée" est un peu contraire à l'esprit de TDD - la question pourrait plutôt être "Puis-je tester une exigence dont l'implémentation inclut des méthodes privées". Et la réponse à cette question est clairement oui.
Dawood ibn Kareem
6

Je crois comprendre que les méthodes privées ne sont pas testables

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.

Jalayn
la source
5

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.

Chris Pitman
la source
C’est une excellente idée: "... que l’usine 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 façon externe le type d’événements pour lequel CUT doit être testé. "
chiot
5

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.

  1. Réduisez le nombre de méthodes et de propriétés privées que vous utilisez. De toute façon, la plupart des choses que vous avez besoin que votre classe fasse ont tendance à être exposées publiquement. Alors réfléchissez à la question de savoir si vous avez vraiment besoin de rendre privée cette méthode intelligente.
  2. Réduisez au minimum la quantité de code dans vos méthodes privées - vous devriez vraiment le faire de toute façon - et testez indirectement où vous pouvez via le comportement d'autres méthodes. Vous ne vous attendez jamais à obtenir une couverture de test à 100% et vous devrez peut-être vérifier quelques valeurs à la main via le débogueur. Utiliser des méthodes privées pour lancer des exceptions peut facilement être testé indirectement. Il peut être nécessaire de tester les propriétés privées manuellement ou via une autre méthode.
  3. Si la vérification indirecte ou manuelle ne vous convient pas, ajoutez un événement protégé et un accès via une interface pour exposer certains éléments privés. Cela "plie" efficacement les règles d'encapsulation, mais évite de devoir envoyer le code qui exécute vos tests. L'inconvénient est que cela peut entraîner un petit code interne supplémentaire pour s'assurer que l'événement sera déclenché en cas de besoin.
  4. Si vous estimez qu'une méthode publique n'est pas suffisamment "sécurisée", voyez s'il existe des moyens d'implémenter un type de processus de validation dans vos méthodes pour limiter leur utilisation. Il y a de fortes chances que, pendant que vous réfléchissez à cela, réfléchissez à une meilleure façon de mettre en œuvre vos méthodes ou voyez une autre classe commencer à prendre forme.
  5. Si de nombreuses méthodes privées effectuent des "opérations" sur vos méthodes publiques, il se peut qu'une nouvelle classe soit en attente d'extraction. Vous pouvez tester cela directement en tant que classe séparée, mais l'implémenter en tant que composite en privé dans la classe qui l'utilise.

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.

S.Robins
la source
4

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.

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.

Winston Ewert
la source
4

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.

Chance
la source
3

À 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.

siamii
la source
... en supposant qu'il s'agisse de Java.
Dawood ibn Kareem
ou internalen .net.
CodesInChaos
2

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.

Karl Bielefeldt
la source
2

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.

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.

Erik Dietrich
la source
2

É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