web-dev-qa-db-fra.com

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

J'apprends maintenant le TDD. Je crois comprendre que les méthodes privées ne sont pas testables 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 OOP pendant un certain temps. Je crois comprendre que les méthodes privées rendent les objets plus encapsulés, donc plus résistants aux changements et aux erreurs. Ainsi, ils devraient être utilisés par défaut et uniquement les méthodes qui la question aux clients devrait être rendue publique.

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

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

Est-ce à dire que TDD est en contradiction avec l'encapsulation? Quel est l'équilibre approprié? Je suis enclin à rendre publiques la plupart ou la totalité de mes méthodes maintenant ...

106
pup

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.

[méthodes privées] ne devrait pas vous inquiéter 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 un détail d'implémentation qui pourrait changer au cours de tout cycle de re-facteur. Il devrait être possible de re-factoriser sans changer l'interface ou le comportement black-box. En fait, cela fait partie des avantages de TDD, la facilité avec laquelle vous pouvez générer la confiance que les changements 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 non testable.

Même si la classe n'a pas de méthodes publiques, ses gestionnaires d'événements sont: interface publique, et c'est contre que = interface publique que vous pouvez tester.

Puisque les événements sont l'interface, ce sont les événements que vous devrez générer pour tester cet objet.

Cherchez à utiliser objets fictifs comme colle pour votre système de test. Il devrait être possible de créer un objet simulé simple qui génère un événement et capte le changement d'état résultant (possible par un autre objet fantôme 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 méfiant d'exposer l'état interne.

Est-ce à dire que TDD est en contradiction avec l'encapsulation? Quel est l'équilibre approprié?

Absolument pas.

TDD ne devrait pas changer l'implémentation de vos classes autrement que peut-être pour les simplifier (en appliquant YAGNI à partir d'un point antérieur).

La meilleure pratique avec TDD est identique à la meilleure pratique sans TDD, vous venez de découvrir pourquoi plus tôt, car vous utilisez l'interface pendant que vous la développez.

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

Ce serait plutôt jeter le bébé avec l'eau du bain.

Vous ne devriez pas besoin rendre toutes les méthodes publiques afin de pouvoir développer de manière TDD. Voir mes notes ci-dessous pour voir si vos méthodes privées ne sont vraiment pas testables.

Un examen plus détaillé des tests de méthodes privées

Si vous devez absolument tester un peu le comportement privé d'une classe, selon la langue/l'environnement, vous pouvez avoir trois options:

  1. Mettez les tests dans la classe que vous souhaitez tester.
  2. Placez les tests dans un autre fichier de classe/source 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 séparer le code de test et de production, tout en autorisant l'accès au code de test aux méthodes privées du code de production.

De toute évidence, la 3e option est de loin la meilleure.

1) Mettez les tests dans la classe que vous souhaitez tester (pas idéal)

Le stockage des cas de test dans le même fichier de classe/source que le code de production testé est l'option la plus simple. Mais sans beaucoup de directives ou d'annotations de pré-processeur, vous vous retrouverez avec votre code de test gonflant votre code de production inutilement, et selon la façon dont vous avez structuré votre code, vous pouvez finir par exposer accidentellement l'implémentation interne aux utilisateurs de ce code.

2) Exposez les méthodes privées que vous souhaitez tester en tant que méthodes publiques (ce n'est vraiment pas une bonne idée)

Comme suggéré, cette pratique est très mauvaise, détruit l'encapsulation et exposera implémentation interne aux utilisateurs du code.

3) Utilisez un meilleur environnement de test (meilleure option, si elle est disponible)

Dans le monde Eclipse, 3. peut être atteint en utilisant fragments . Dans le monde C #, nous pourrions utiliser classes partielles . D'autres langues/environnements ont souvent des fonctionnalités similaires, il vous suffit de les trouver.

En supposant aveuglément que 1. ou 2. sont les seules options, il est probable que le logiciel de production soit gonflé de code de test ou d'interfaces de classe désagréables qui lavent leur linge sale en public. * 8 ')

  • Dans l'ensemble, il est préférable de ne pas tester la mise en œuvre privée.
55
Mark Booth

Bien sûr, vous pouvez avoir des méthodes privées, et bien sûr, vous pouvez les tester.

Soit il y a certains moyen pour exécuter la méthode privée, auquel cas vous pouvez la tester de cette façon, soit il y a non moyen pour exécuter la méthode privée, dans quel cas: pourquoi diable essayez-vous de le tester, supprimez simplement 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 non testable.

Pourquoi serait-ce impossible à tester? Si la méthode est invoquée en réaction à un événement, il suffit que le test alimente l'objet en un événement approprié.

Il ne s'agit pas de n'avoir aucune méthode privée, il ne s'agit pas de rompre 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 des événements.

Pour le cas le plus courant des méthodes d'assistance privées, 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 qui échoue et que vos tests testent l'API publique, tout le nouveau code que vous écrivez sera généralement public. Les méthodes privées n'apparaissent qu'à la suite d'un Extract Method Refactoring, 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 également la méthode privée, 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 déjà testées également.

76
Jörg W Mittag

Lorsque vous créez une nouvelle classe dans votre code, vous le faites pour répondre à certaines exigences. Les exigences disent ce que le code doit faire, pas comment . Cela permet de comprendre facilement pourquoi la plupart des tests ont lieu au niveau des méthodes publiques.

Grâce à des tests, nous vérifions que le code fait ce qu'il est censé faire, lève les exceptions appropriées lorsque cela est prévu, etc. Nous ne nous soucions pas vraiment de la façon dont le code est mis en œuvre par le développeur. Bien que nous ne nous soucions pas de l'implémentation, c'est-à-dire de la façon dont le code fait ce qu'il fait, il est logique d'éviter de tester des méthodes privées.

Quant aux tests de classes qui n'ont pas de méthodes publiques 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 enverra l'événement et vérifiera que le fichier journal est écrit.

Enfin et surtout, dans certains cas, il est parfaitement valable de tester des méthodes privées. C'est pourquoi par exemple dans .NET, vous pouvez tester non seulement des classes publiques, mais aussi des classes privées, même si la solution n'est pas aussi simple que pour les méthodes publiques.

26
Arseni Mourzenko

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 a extrait une partie du code dans une méthode privée intelligemment nommée.

Quelle que soit la façon dont la méthode publique est écrite, votre code de test doit couvrir tous les chemins. Si vous découvrez après vos tests que l'une des instructions de branche (if/switch) dans une méthode privée n'a jamais été couverte dans vos tests, alors vous avez un problème. Soit vous avez manqué un cas et l'implémentation est correcte OR l'implémentation est incorrecte, et cette branche n'aurait jamais dû exister en fait.

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.

6
Jalayn

Votre exemple est toujours parfaitement testable tant que vous utilisez l'injection de dépendance pour fournir les instances avec lesquelles votre CUT interagit. Ensuite, vous pouvez utiliser une maquette, générer les événements d'intérêt, puis observer si la CUT prend ou non les bonnes actions sur ses dépendances.

D'un autre côté, si vous avez un langage avec une bonne prise en charge d'événements, vous pouvez prendre un chemin légèrement différent. Je n'aime pas quand les objets s'abonnent aux événements eux-mêmes, à la place, la fabrique qui crée l'objet relie les événements aux méthodes publiques de l'objet. Il est plus facile à tester et rend visible de l'extérieur les types d'événements pour lesquels la CUT doit être testée.

5
Chris Pitman

Vous ne devriez pas avoir besoin de renoncer à utiliser des méthodes privées. Il est parfaitement raisonnable de les utiliser, mais du point de vue des tests, il est plus difficile de les tester directement sans interrompre l'encapsulation ou ajouter du code spécifique au test à vos classes. L'astuce consiste à minimiser ce que vous savez qui va faire se tortiller vos tripes parce que vous avez l'impression d'avoir sali votre code.

Ce sont les choses que je garde à l'esprit pour essayer de parvenir à un équilibre viable.

  1. Réduisez le nombre de méthodes et de propriétés privées que vous utilisez. La plupart des choses que vous avez besoin de faire pour votre classe ont tendance à devoir être exposées publiquement de toute façon, alors demandez-vous si vous avez vraiment besoin de rendre cette méthode intelligente privée.
  2. Minimisez 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 manuellement quelques valeurs via le débogueur. L'utilisation de méthodes privées pour lever des exceptions peut facilement être testée indirectement. Les propriétés privées peuvent devoir être testé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 accédez-y via une interface pour exposer certains éléments privés. Cela "contourne" effectivement les règles d'encapsulation, mais évite d'avoir à expédier réellement du code qui exécute vos tests. L'inconvénient est que cela peut entraîner un peu de code interne supplémentaire pour s'assurer que l'événement sera déclenché en cas de besoin.
  4. Si vous pensez qu'une méthode publique n'est pas suffisamment "sécurisée", voyez s'il existe des moyens de mettre en œuvre une sorte de processus de validation dans vos méthodes pour limiter la façon dont elles sont utilisées. Il y a de fortes chances que pendant que vous y réfléchissez, pensez à une meilleure façon d'implémenter vos méthodes, ou vous verrez une autre classe commencer à prendre forme.
  5. Si vous avez beaucoup de méthodes privées faisant des "trucs" pour vos méthodes publiques, il peut y avoir une nouvelle classe en attente d'être extraite. Vous pouvez le tester directement en tant que classe distincte, mais l'implémenter en tant que composite en privé dans la classe qui l'utilise.

Pensez latéralement. Gardez vos classes petites et vos méthodes plus petites, et utilisez beaucoup de composition. Cela ressemble à plus de travail, mais à la fin, vous vous retrouverez avec des éléments plus testables individuellement, vos tests seront plus simples, vous aurez plus d'options pour utiliser des simulations simples à la place d'objets réels, grands et complexes, espérons bien- du code factorisé et faiblement couplé, et plus important encore, vous vous donnerez 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 vérifier individuellement pour chaque classe, et vous avez tendance à réduire naturellement les spaghettis de code qui peuvent parfois se produire lorsqu'une classe devient grande et a beaucoup de comportement de code interdépendant en interne.

5
S.Robins

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

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. Demandez-lui d'appeler un objet simulé et vous pouvez 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. Peu nous importe ce qui se passe à l'intérieur d'un objet. Donc non, vous ne devriez plus avoir de méthodes publiques auparavant.

4
Winston Ewert

J'ai également eu du mal avec ce même problème. Vraiment, la façon de le contourner est la suivante: Comment pensez-vous que le reste de votre programme s'interface avec cette classe? Testez votre classe en conséquence. Cela vous obligera à concevoir votre classe en fonction de la façon dont Le reste du programme s'interface avec lui et encouragera en fait l'encapsulation et la bonne conception de votre classe.

4
Chance

Au lieu d'utiliser un modificateur par défaut à usage privé. Ensuite, vous pouvez tester ces méthodes individuellement, pas seulement couplées à des méthodes publiques. Cela nécessite que vos tests aient la même structure de package que votre code principal.

3
siamii

Quelques méthodes privées ne sont généralement pas un problème. Vous les testez simplement 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 mauvaise cohésion. Votre classe devrait avoir une responsabilité cohésive, et souvent les gens rendent les 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 un grand nombre d'appels de base de données en réponse à ces événements. Etant donné qu'il est manifestement une mauvaise pratique d'instancier un gestionnaire d'événements pour effectuer des appels à la base de données, la tentation est de rendre toutes les appels liés à la base de données des méthodes privées, alors qu'ils devraient vraiment être extraits dans une classe distincte.

2
Karl Bielefeldt

Est-ce à dire que TDD est en contradiction avec l'encapsulation? Quel est l'équilibre approprié? Je suis enclin à rendre publiques la plupart ou la totalité de mes méthodes.

TDD n'est pas en contradiction avec l'encapsulation. Prenez l'exemple le plus simple d'une méthode ou d'une propriété getter, selon la langue de votre choix. Disons que j'ai un objet Customer et que je veux qu'il ait un champ Id. Le premier test que je vais écrire est celui qui dit quelque chose comme "customer_id_initializes_to_zero". Je définis le getter pour lever une exception non implémentée et regarder l'échec du test. Ensuite, la chose la plus simple que je puisse faire pour réussir ce test est que le getter retourne zéro.

À partir de là, je passe à d'autres tests, probablement ceux qui impliquent que l'ID client est un domaine fonctionnel réel. À un moment donné, je dois probablement créer un champ privé que la classe client utilise pour garder une trace de ce qui doit être retourné par le getter. Comment puis-je suivre exactement cela? Est-ce un simple support int? Dois-je garder une trace d'une chaîne, puis la convertir en int? Dois-je garder une trace de 20 pouces et les moyenne? Le monde extérieur s'en fiche - et vos tests TDD s'en moquent. C'est un détail encapsulé.

Je pense que ce 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 granulaires de la classe. Donc, vous ne cherchez pas à tester cette méthode DoSomethingToFoo() instancie une barre, appelle une méthode dessus, ajoute deux à l'une de ses propriétés, etc. Vous testez cela après avoir muté l'état de votre , un accesseur d'état a changé (ou pas). C'est le schéma général de vos tests: "quand je fais X à ma classe sous test, je peux par la suite observer Y". La façon dont il arrive à Y ne fait pas partie des préoccupations des tests, et c'est ce qui est encapsulé et c'est pourquoi TDD n'est pas en contradiction avec l'encapsulation.

2
Erik Dietrich

Évitez d'utiliser? Non.
Évitez commençant par? Oui.

Je remarque que vous ne vous êtes pas demandé s'il était acceptable d'avoir des classes abstraites avec TDD; si vous comprenez comment les classes abstraites émergent pendant TDD, 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 comme vous ne pouvez pas tester directement 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 des fonctionnalités communes au fur et à mesure.

2
user34530

Votre classe a des exigences. En gros, votre classe devrait avoir des méthodes publiques qui correspondent aux exigences, et pas d'autres.

Et votre classe a besoin de code pour implémenter les exigences. Selon les exigences, il peut avoir besoin de beaucoup de code. Et pour diverses raisons, vos méthodes ne devraient pas être trop volumineuses.

Si vous n'insistez sur aucune méthode privée, alors tout le code doit être mis dans les méthodes publiques, ce qui est évidemment un déchet. Vous ajoutez donc exactement autant de méthodes privées que nécessaire pour avoir une classe avec un bon code. La testabilité n'est pas tout. En fait, la testabilité n'est rien si vous violez de bons principes de codage et que vous vous retrouvez avec un code toujours trop bogué pour passer vos tests.

Les méthodes sont rendues privées pour empêcher chaque ancien bit de code de les appeler. Cela ne signifie pas que vous ne voulez pas les tester. Par exemple, sur MacOS/iOS, vous marquez simplement une méthode comme @testable et elle peut être appelée comme une méthode publique lorsque vous créez des tests unitaires ou d'autres tests, mais pas lorsque vous créez une application.

0
gnasher729