TDD Red-Green-Refactor et si / comment tester des méthodes devenues privées

91

pour autant que je sache, la plupart des gens semblent convenir que les méthodes privées ne doivent pas être testées directement, mais plutôt par le biais de méthodes publiques. Je peux comprendre leur point de vue, mais cela me pose quelques problèmes lorsque j'essaie de suivre les "Trois lois du TDD" et d’utiliser le cycle "Rouge - vert - refactor". Je pense que cela s'explique mieux par un exemple:

Pour le moment, j'ai besoin d'un programme capable de lire un fichier (contenant des données séparées par des tabulations) et de filtrer toutes les colonnes contenant des données non numériques. Je suppose qu'il existe probablement déjà quelques outils simples pour le faire, mais j'ai décidé de le mettre en œuvre moi-même, principalement parce que je pensais que ce pourrait être un projet intéressant et propre pour moi de m'entraîner avec TDD.

Alors, premièrement, je "mets le chapeau rouge", c’est-à-dire qu’il me faut un test qui échoue. J'ai pensé qu'il me faudrait une méthode qui trouve tous les champs non numériques dans une ligne. Donc, j’écris un test simple, bien sûr, la compilation n’est pas immédiate, alors j’écris la fonction elle-même, et après quelques cycles (rouge / vert), j’ai une fonction fonctionnelle et un test complet.

Ensuite, je continue avec une fonction, "rassemblerNonNumericColumns" qui lit le fichier, une ligne à la fois, et appelle ma fonction "findNonNumericFields" sur chaque ligne pour rassembler toutes les colonnes qui doivent éventuellement être supprimées. Un couple de cycles rouge-vert, et j'ai terminé, d'avoir à nouveau une fonction de travail et un test complet.

Maintenant, je pense que je devrais refactoriser. Étant donné que ma méthode "findNonNumericFields" a été conçue uniquement parce que je pensais en avoir besoin lors de la mise en oeuvre de "collecteNonNumericColumns", il me semble raisonnable de laisser "findNonNumericFields" privé. Cependant, cela romprait mes premiers tests, car ils n'auraient plus accès à la méthode qu'ils testaient.

Donc, je me retrouve avec une méthode privée, et une suite de tests qui le teste. Étant donné que tant de personnes conseillent de ne pas tester les méthodes privées, j'ai l'impression que je me suis retrouvé dans un coin. Mais où ai-je échoué?

Je suppose que j'aurais pu commencer à un niveau supérieur, en écrivant un test qui teste ce qui deviendra finalement ma méthode publique (c'est-à-dire, findAndFilterOutAllNonNumericalColumns), mais cela semble un peu contraire à l'intérêt de TDD (du moins d'après Oncle Bob) : Que vous devez commuter constamment entre les tests d’écriture et le code de production et que, à tout moment, tous vos tests fonctionnent à la dernière minute. Parce que si je commence par écrire un test pour une méthode publique, il faudra plusieurs minutes (ou même plusieurs heures, voire plusieurs jours dans des cas très complexes) pour obtenir tous les détails des méthodes privées afin que le test teste le public la méthode passe.

Alors que faire? TDD (avec le cycle rapide rouge-vert-refactor) est-il tout simplement incompatible avec les méthodes privées? Ou y a-t-il un défaut dans ma conception?

Henrik Berg
la source
2
Soit ces deux fonctionnalités sont suffisamment différentes pour constituer des unités différentes (dans ce cas, les méthodes privées devraient probablement être sur leurs propres classes), soit elles sont la même unité, auquel cas je ne vois pas pourquoi vous écrivez des tests. comportement interne à l'unité. En ce qui concerne l'avant-dernier paragraphe, je ne vois pas le conflit. Pourquoi auriez-vous besoin d'écrire une méthode privée complexe complète pour réussir un scénario de test? Pourquoi ne pas le chasser progressivement via la méthode publique, ou le commencer en ligne puis l'extraire?
Ben Aaronson
26
Pourquoi les gens prennent-ils les idiomes et les clichés des livres de programmation et des blogs en tant que directives sur la manière de programmer me dépasse.
AK_
7
Je n'aime pas le TDD pour cette raison: si vous vous trouvez dans une nouvelle zone, vous devrez faire beaucoup de travail supplémentaire pour essayer de déterminer comment l'architecture devrait être et comment certaines choses fonctionnent. D'un autre côté: si vous vous trouvez dans un domaine que vous connaissez déjà, l'écriture des tests sera un avantage en plus de vous gêner, car intellisense ne comprend pas pourquoi vous écrivez du code non compilable. Je suis un plus grand partisan de la conception, de l'écriture puis des tests unitaires.
Jeroen Vannevel
1
"la plupart des gens semblent convenir que les méthodes privées ne doivent pas être testées directement" - non, testez directement une méthode si cela vous semble judicieux. Cachez-le comme privates'il était logique de le faire.
osa

Réponses:

44

Unités

Je pense que je peux identifier exactement où le problème a commencé:

J'ai pensé qu'il me faudrait une méthode qui trouve tous les champs non numériques dans une ligne.

Cela devrait être immédiatement suivi de vous demander "Sera-ce une unité testable distincte gatherNonNumericColumnsou une partie de la même?"

Si la réponse est « oui, séparez », votre plan d’action est simple: cette méthode doit être publique sur une classe appropriée pour pouvoir être testée comme une unité. Votre mentalité est quelque chose comme "J'ai besoin de tester une méthode et de conduire une autre méthode"

D'après ce que vous dites, vous avez pensé que la réponse était " non, une partie de la même chose ". À ce stade, votre plan ne devrait plus être d'écrire et de tester findNonNumericFields puis d' écrire gatherNonNumericColumns. Au lieu de cela, il devrait être simplement d'écrire gatherNonNumericColumns. Pour l'instant, findNonNumericFieldsvous ne devez penser qu'à faire partie de la destination lorsque vous choisissez votre prochain scénario de test rouge et effectuez votre refactoring. Cette fois, votre mentalité est la suivante: "Je dois tester une méthode et je ne dois pas oublier que mon implémentation finale inclura probablement cette autre méthode".


Garder un cycle court

Faire ce qui précède ne devrait pas conduire aux problèmes que vous décrivez dans votre avant-dernier paragraphe:

Parce que si je commence par écrire un test pour une méthode publique, il faudra plusieurs minutes (ou même plusieurs heures, voire plusieurs jours dans des cas très complexes) pour obtenir tous les détails des méthodes privées afin que le test teste le public la méthode passe.

À aucun moment cette technique ne vous oblige à écrire un test rouge qui ne deviendra vert que lorsque vous implémenterez l'intégralité de findNonNumericFields. Beaucoup plus vraisemblablement, findNonNumericFieldscommencera par un code en ligne dans la méthode publique que vous testez, qui sera construit au cours de plusieurs cycles et éventuellement extrait lors d'un refactoring.


Feuille de route

Pour vous donner une feuille de route approximative pour cet exemple particulier, je ne connais pas exactement les cas de test que vous avez utilisés, mais dites que vous écriviez en gatherNonNumericColumnstant que méthode publique. Ensuite, il est fort probable que les cas de test soient identiques à ceux pour lesquels vous avez écrit findNonNumericFields, chacun utilisant une table avec une seule ligne. Lorsque ce scénario à une ligne était complètement implémenté et que vous souhaitiez écrire un test pour vous forcer à extraire la méthode, vous écririez un cas à deux lignes qui nécessiterait l'ajout de votre itération.

Ben Aaronson
la source
2
Je pense que c'est la réponse ici. En adoptant TDD dans un environnement POO, j'ai souvent eu du mal à surmonter mes propres instincts ascendants. Oui, les fonctions devraient être petites, mais c'est après refactorisation. Avant, ils peuvent être d’énormes monolithes. +1
João Mendes
2
@ JoãoMendes Eh bien, je ne suis pas sûr que vous deviez atteindre l'état d'un énorme monolithe avant de refactoriser, en particulier sur de très courts cycles RGR. Mais oui, dans une unité testable, travailler de bas en haut peut conduire aux problèmes décrits par le PO.
Ben Aaronson
1
OK, je pense comprendre ce qui ne va pas maintenant. Merci beaucoup à vous tous (cette réponse est marquée, mais la plupart des autres réponses sont également utiles)
Henrik Berg
66

Beaucoup de gens pensent que les tests unitaires sont basés sur des méthodes; ce n'est pas. Il devrait être basé sur la plus petite unité qui a du sens. Pour la plupart des choses, cela signifie que la classe est ce que vous devriez tester en tant qu'entité entière. Pas des méthodes individuelles sur elle.

Évidemment, vous allez appeler des méthodes sur la classe, mais vous devriez penser aux tests comme s’appliquant à l’objet boîte noire que vous avez, de sorte que vous devriez pouvoir voir que toutes les opérations logiques fournies par votre classe; Ce sont les choses que vous devez tester. Si votre classe est si grande que l'opération logique est trop complexe, vous avez un problème de conception à résoudre en premier.

Une classe avec mille méthodes peut sembler testable, mais si vous ne testez que chaque méthode individuellement, vous ne testez pas vraiment la classe. Certaines classes peuvent nécessiter d'être dans un certain état avant qu'une méthode soit appelée, par exemple une classe de réseau nécessitant une connexion établie avant l'envoi de données. La méthode d'envoi des données ne peut pas être considérée indépendamment de la classe entière.

Vous devriez donc voir que les méthodes privées ne sont pas pertinentes pour les tests. Si vous ne pouvez pas exercer vos méthodes privées en appelant l'interface publique de votre classe, ces méthodes privées sont inutiles et ne seront de toute façon pas utilisées.

Je pense que beaucoup de gens essaient de transformer les méthodes privées en unités testables car il semble facile d'exécuter des tests pour elles, mais cela amène la granularité des tests trop loin. Martin Fowler dit

Bien que je commence par l'idée que l'unité est une classe, je prends souvent des classes très proches et je les traite comme une seule unité.

ce qui a beaucoup de sens pour un système orienté objet, les objets étant conçus pour être des unités. Si vous souhaitez tester des méthodes individuelles, vous devriez peut-être créer un système procédural comme C ou une classe entièrement composée de fonctions statiques.

gbjbaanb
la source
14
Pour moi, il semble que cette réponse ignore complètement l'approche de la TDD dans la question du PO. C'est simplement une répétition du mantra "ne testez pas les méthodes privées", mais cela n'explique pas comment TDD - qui est basé sur une méthode - pourrait fonctionner avec une approche de test unitaire non basée sur une méthode.
Doc Brown
6
@ DocBrown non, cela lui répond complètement en disant "ne pas trop granualiser" vos unités et vous rendre la vie difficile. TDD est pas méthode basée, elle est unité basée où une unité est tout sens. Si vous avez une bibliothèque C, alors oui chaque unité sera une fonction. Si vous avez une classe, l'unité est un objet. Comme le dit Fowler, parfois une unité est constituée de plusieurs classes étroitement liées. Je pense que beaucoup de gens considèrent les tests unitaires comme méthode par méthode tout simplement parce que certains outils stupides génèrent des stubs basés sur des méthodes.
gbjbaanb
3
@gbjbaanb: essayez de suggérer un test qui permette au PO de mettre en œuvre son "rassemblement de champs non numériques dans une ligne" en utilisant d'abord un TDD pur, sans que l'interface publique de la classe qu'il a l'intention d'écrire soit déjà écrite.
Doc Brown
8
Je suis d'accord avec @DocBrown ici. Le problème de l'interrogateur n'est pas qu'il souhaite plus de granularité de test qu'il ne peut en obtenir sans tester des méthodes privées. C'est qu'il a essayé de suivre une approche TDD stricte et - sans planification en tant que telle - qui l'a conduit à se heurter à un mur où il découvre soudainement qu'il a passé de nombreux tests pour déterminer ce qui devrait être une méthode privée. Cette réponse n'aide pas avec ça. C'est une bonne réponse à une question, mais pas à celle-ci.
Ben Aaronson
7
@ Matthew: Son erreur est qu'il a écrit la fonction en premier lieu. Dans l'idéal, il aurait dû écrire la méthode publique sous forme de code spaghetti, puis la reformer en une fonction privée dans le cycle de refactorisation - et non la marquer comme privée dans le cycle de refactorisation.
Slebetman
51

Le fait que vos méthodes de collecte de données soient suffisamment complexes pour mériter des tests et se séparent suffisamment de votre objectif principal pour constituer des méthodes qui leur sont propres plutôt que de faire partie d'une boucle pointe vers la solution: rendez ces méthodes non privées, mais membres d'une autre classe. qui fournit des fonctionnalités de collecte / filtrage / tabulation.

Ensuite, vous écrivez à un endroit des tests portant sur les aspects factices de la classe d’assistance (par exemple, "distinguer les chiffres des caractères"), puis sur votre objectif principal ("obtenir les chiffres de vente") à un autre endroit. Il n'est pas nécessaire de répéter les tests de filtrage de base dans les tests pour votre logique métier normale.

En règle générale, si votre classe qui fait une chose contient du code complet pour faire une autre chose qui est requise pour, mais distincte de son objectif principal, ce code doit vivre dans une autre classe et être appelé via des méthodes publiques. Il ne devrait pas être caché dans les coins privés d'une classe qui contient accidentellement ce code. Cela améliore la testabilité et la compréhensibilité en même temps.

Kilian Foth
la source
Oui je suis d'accord avec toi. Mais votre première déclaration me pose un problème: les parties "assez complexe" et "suffisamment séparée". En ce qui concerne "assez complexe": j'essaie de réaliser un cycle rouge-vert rapide, ce qui signifie que je ne peux coder que pendant au plus une minute à la fois avant de passer aux tests (ou l'inverse). Cela signifie que mes tests seront très fins. Je pensais que c’était l’un des avantages du TDD, mais j’en ai peut-être fait trop pour que cela devienne un désavantage.
Henrik Berg
En ce qui concerne "assez séparé": j'ai appris (encore une fois de onclebob) que les fonctions devraient être petites, et qu'elles devraient être plus petites que cela. Donc, fondamentalement, j'essaie de créer des fonctions de 3-4 lignes. Ainsi, plus ou moins toutes les fonctionnalités sont séparées en méthodes, peu importe leur taille.
Henrik Berg
Quoi qu’il en soit, j’ai le sentiment que les aspects liés à la collecte de données (par exemple, findNonNumericFields) devraient être réellement privés. Et si je le classe dans une autre classe, je devrai le rendre public de toute façon, alors je ne vois pas trop l'intérêt de cela.
Henrik Berg
6
@HenrikBerg pense d'abord pourquoi vous avez des objets - ils ne constituent pas un moyen pratique de regrouper des fonctions, mais sont des unités autonomes qui facilitent l'utilisation de systèmes complexes. Par conséquent, vous devriez penser à tester la classe comme une chose.
gbjbaanb
@ gbjbaanb Je dirais qu'ils sont tous deux identiques.
RubberDuck
29

Personnellement, j’ai le sentiment que vous êtes allé trop loin dans la mise en œuvre lorsque vous avez rédigé les tests. Vous avez supposé que vous auriez besoin de certaines méthodes. Mais avez-vous vraiment besoin d'eux pour faire ce que la classe est censée faire? La classe échouerait-elle si quelqu'un venait les chercher et les refacturait en interne? Si vous utilisiez la classe (et que cela devrait être l'état d'esprit du testeur à mon avis), vous pourriez vraiment vous en soucier s'il existe une méthode explicite pour vérifier les nombres.

Vous devriez tester l'interface publique d'une classe. L'implémentation privée est privée pour une raison. Cela ne fait pas partie de l'interface publique parce que ce n'est pas nécessaire et peut changer. C'est un détail de mise en œuvre.

Si vous écrivez des tests sur l'interface publique, vous ne rencontrerez jamais le problème rencontré. Vous pouvez soit créer des scénarios de test pour l'interface publique qui couvrent vos méthodes privées (super), soit vous ne pouvez pas. Dans ce cas, il serait peut-être temps de réfléchir sérieusement aux méthodes privées et de les supprimer complètement si elles ne peuvent pas être atteintes de toute façon.

nvoigt
la source
1
Les "détails d'implémentation" sont des choses comme "ai-je utilisé un XOR ou une variable temporaire pour permuter les ints entre les variables". Les méthodes protégées / privées ont des contrats, comme toute autre chose. Ils prennent des intrants, les utilisent et génèrent des extrants, sous certaines contraintes. Tout ce qui a un contrat doit en fin de compte être testé - pas nécessairement pour ceux qui consomment votre bibliothèque, mais pour ceux qui le maintiennent et le modifient après vous. Ce n'est pas parce que ce n'est pas "public" que cela ne fait pas partie d' une API.
Knetic
11

Vous ne faites pas de TDD en fonction de ce que vous attendez de la classe en interne.

Vos cas de test doivent être basés sur ce que la classe / fonctionnalité / programme doit faire au monde externe. Dans votre exemple, l’utilisateur appellera-t-il jamais votre classe de lecteurs pourfind all the non-numerical fields in a line?

Si la réponse est "non", alors c'est un mauvais test pour écrire en premier lieu. Vous souhaitez écrire le test sur la fonctionnalité au niveau de la classe / interface - pas le niveau "que la méthode de classe devra-t-elle implémenter pour que cela fonctionne", qui correspond à votre test.

Le flux de TDD est:

  • rouge (que font la classe / objet / fonction / etc au monde extérieur)
  • vert (écrivez le code minimal pour que cette fonction de monde externe fonctionne)
  • refactor (quel est le meilleur code pour que cela fonctionne)

Ce n'est pas à faire "parce que j'aurai besoin de X à l'avenir comme méthode privée, laissez-moi l'implémenter et le tester en premier". Si vous vous trouvez en train de faire cela, vous ne réalisez pas correctement l'étape "rouge". Cela semble être votre problème ici.

Si vous vous trouvez fréquemment en train de passer des tests sur des méthodes qui deviennent des méthodes privées, vous effectuez l'une des opérations suivantes:

  • Pas assez bien comprendre vos cas d'utilisation d'interface / niveau public suffisamment bien pour écrire un test pour eux
  • Changer radicalement votre conception et refactoriser plusieurs tests (ce qui peut être une bonne chose, selon que cette fonctionnalité a été testée lors de tests plus récents)
Enderland
la source
9

Vous rencontrez une idée fausse commune avec les tests en général.

La plupart des personnes qui commencent à tester commencent par penser de cette façon:

  • écrire un test pour la fonction F
  • implémenter F
  • écrire un test pour la fonction G
  • implémenter G en utilisant un appel à F
  • écrire un test pour une fonction H
  • implémenter H en utilisant un appel à G

etc.

Le problème ici est que vous n'avez en fait pas de test unitaire pour la fonction H. Le test qui est censé tester H teste en réalité H, G et F en même temps.

Pour résoudre ce problème, vous devez comprendre que les unités testables ne doivent jamais dépendre les unes des autres, mais plutôt de leurs interfaces . Dans votre cas, où les unités sont des fonctions simples, les interfaces ne sont que leur signature d'appel. Vous devez donc implémenter G de manière à ce qu’il puisse être utilisé avec toute fonction ayant la même signature que F.

Cela dépend exactement de votre langage de programmation. Dans de nombreuses langues, vous pouvez passer des fonctions (ou des pointeurs sur celles-ci) comme arguments à d'autres fonctions. Cela vous permettra de tester chaque fonction séparément.

initcrash
la source
3
J'aimerais pouvoir voter autant de fois. Je résumerais simplement le fait que vous n’avez pas correctement conçu votre solution.
Justin Ohms
Dans un langage comme C, cela a du sens, mais pour les langages OO où l'unité doit généralement être une classe (avec des méthodes publiques et privées), vous devez alors tester la classe, et non toutes les méthodes privées de manière isolée. Isoler vos cours, oui. Isoler les méthodes dans chaque classe, non.
gbjbaanb
8

Les tests que vous écrivez lors du développement piloté par les tests sont supposés vérifier qu'une classe implémente correctement son API publique, tout en veillant à ce que cette API publique soit facile à tester et à utiliser.

Vous pouvez bien sûr utiliser des méthodes privées pour implémenter cette API, mais il n'est pas nécessaire de créer des tests via TDD - la fonctionnalité sera testée car l'API publique fonctionnera correctement.

Supposons maintenant que vos méthodes privées soient suffisamment compliquées pour mériter des tests autonomes - mais elles n’ont aucun sens si elles font partie de l’API publique de votre classe d’origine. Cela signifie probablement qu'elles doivent être des méthodes publiques sur une autre classe - une méthode dont votre classe d'origine tire parti dans sa propre implémentation.

En testant uniquement l'API publique, vous facilitez considérablement la modification des détails de la mise en œuvre à l'avenir. Les tests inutiles ne vous ennuieront que plus tard quand ils devront être réécrits pour prendre en charge certains refactoring élégants que vous venez de découvrir.

Bill Michell
la source
4

Je pense que la bonne réponse est la conclusion à laquelle vous êtes parvenue à partir des méthodes publiques. Vous commenceriez par écrire un test qui appelle cette méthode. Cela échouerait donc vous créez une méthode avec ce nom qui ne fait rien. Ensuite, vous avez peut-être raison de faire un test qui recherche une valeur de retour.

(Je ne suis pas tout à fait sûr de ce que fait votre fonction. Renvoie-t-il une chaîne avec le contenu du fichier avec les valeurs non numériques supprimées?)

Si votre méthode retourne une chaîne, vous vérifiez cette valeur de retour. Donc, vous continuez simplement à le construire.

Je pense que tout ce qui se passe dans une méthode privée devrait être dans la méthode publique à un moment donné au cours de votre processus, et ensuite uniquement déplacé dans la méthode privée dans le cadre d'une étape de refactoring. Le refactoring ne nécessite pas d'avoir des tests qui échouent, autant que je sache. Vous n'avez besoin que de tests ayant échoué lors de l'ajout de fonctionnalités. Il vous suffit d'exécuter vos tests après le refactor pour vous assurer qu'ils ont tous réussi.

Matt Dyer
la source
3

c'est comme si je m'étais peint dans un coin ici. Mais où ai-je échoué?

Il y a un vieil adage.

Lorsque vous ne parvenez pas à planifier, vous prévoyez d'échouer.

Les gens semblent penser que lorsque vous utilisez TDD, il vous suffit de vous asseoir, d'écrire des tests et que la conception se produira comme par magie. Ce n'est pas vrai Vous devez disposer d'un plan de haut niveau. J'ai constaté que TDD obtenait mes meilleurs résultats lorsque je concevais d'abord l'interface (API publique). Personnellement, je crée un réel interfacequi définit d'abord la classe.

haletant, j'ai écrit du "code" avant d'écrire des tests! Et bien non. Je n'ai pas J'ai écrit un contrat à suivre, un design . Je suppose que vous pourriez obtenir des résultats similaires en notant un diagramme UML sur du papier quadrillé. Le fait est que vous devez avoir un plan. TDD n'est pas autorisé à pirater un morceau de code.

Je me sens vraiment comme "Test First" est un abus de langage. Design d'abord puis testez.

Bien sûr, suivez les conseils donnés par d’autres personnes pour extraire davantage de classes de votre code. Si vous ressentez le besoin de tester les composants internes d'une classe, extrayez-les dans une unité facilement testée et injectez-la.

Canard en caoutchouc
la source
2

Rappelez-vous que les tests peuvent aussi être refondus! Si vous rendez une méthode privée, vous réduisez l'API publique. Il est donc parfaitement acceptable de jeter certains tests correspondants pour cette "fonctionnalité perdue" (complexité réduite de AKA).

D'autres ont dit que votre méthode privée serait appelée dans le cadre de vos autres tests d'API ou qu'elle serait inaccessible et par conséquent supprimable. En fait, les choses sont plus fines si nous pensons aux chemins d'exécution .

Par exemple, si nous avons une méthode publique qui effectue une division, nous pourrions vouloir tester le chemin qui aboutit à une division par zéro. Si nous rendons la méthode privée, nous obtenons un choix: soit nous pouvons considérer le chemin de division par zéro, soit éliminer ce chemin en considérant comment il est appelé par les autres méthodes.

De cette façon, nous pouvons jeter certains tests (par exemple, diviser par zéro) et refactoriser les autres en termes de l’API publique restante. Bien sûr, dans un monde idéal, les tests existants s’occupent de tous les chemins restants, mais la réalité est toujours un compromis;)

Warbo
la source
1
Alors que les autres réponses sont correctes en ce que la méthode privée n'aurait pas dû être écrite dans le cycle rouge, les humains font des erreurs. Et quand vous vous êtes suffisamment trompé dans l'erreur, c'est la solution appropriée.
Slebetman
2

Il arrive parfois qu'une méthode privée devienne une méthode publique d'une autre classe.

Par exemple, vous pouvez avoir des méthodes privées non thread-safe et laisser la classe dans un état temporaire. Ces méthodes peuvent être déplacées vers une classe séparée qui est tenue en privé par votre première classe. Ainsi, si votre classe est une file d'attente, vous pouvez avoir une classe InternalQueue qui a des méthodes publiques et la classe Queue contenir l'instance InternalQueue de manière privée. Cela vous permet de tester la file d'attente interne et de définir clairement les opérations individuelles sur InternalQueue.

(Cela est particulièrement évident lorsque vous imaginez qu'il n'y a pas de classe List et que vous avez essayé d'implémenter les fonctions List en tant que méthodes privées dans la classe qui les utilise.)

Thomas Andrews
la source
2
"Il arrive parfois qu'une méthode privée devienne une méthode publique d'une autre classe." Je ne peux pas insister sur ce point. Parfois, une méthode privée est simplement une autre classe qui revendique sa propre identité.
0

Je me demande pourquoi votre langue ne comporte que deux niveaux de confidentialité, entièrement public et totalement privé.

Pouvez-vous organiser vos méthodes non publiques pour qu'elles soient accessibles par paquets ou quelque chose du genre? Placez ensuite vos tests dans le même package et testez les travaux internes qui ne font pas partie de l'interface publique. Votre système de génération exclura les tests lors de la construction d’un binaire de publication.

Bien sûr, vous devez parfois avoir des méthodes vraiment privées, inaccessibles à autre chose qu'à la classe de définition. J'espère que toutes ces méthodes sont très petites. En général, garder les méthodes réduites (par exemple, au-dessous de 20 lignes) aide beaucoup: les tests, la maintenance et la simple compréhension du code deviennent plus faciles.

9000
la source
3
Changer un modificateur d'accès à une méthode juste pour exécuter des tests est une situation dans laquelle une queue bouge un chien. Je pense que tester les parties internes d'une unité ne fait que rendre plus difficile la refactorisation plus tard. Tester une interface publique, au contraire, est très utile car cela fonctionne comme un "contrat" ​​pour une unité.
scriptin
Vous n'avez pas besoin de changer le niveau d'accès d'une méthode. Ce que j'essaie de dire, c'est que vous avez un niveau d'accès intermédiaire qui permet d'écrire certains codes, y compris des tests, plus facilement, sans passer de contrat public. Bien sûr, vous devez tester l'interface publique, mais il est parfois avantageux de tester certains mécanismes internes de manière isolée.
9000
0

J'ai parfois eu recours à des méthodes privées à protéger pour permettre des tests plus fins (plus strictes que l'API publique exposée). Cela devrait être l'exception (espérons-le très rare) plutôt que la règle, mais cela peut être utile dans certains cas spécifiques que vous pourriez rencontrer. En outre, c’est quelque chose que vous ne voudriez pas prendre en compte du tout lors de la construction d’une API publique, mais plutôt une «astuce» que vous pouvez utiliser avec un logiciel à usage interne dans ces rares situations.

Brian Knoblauch
la source
0

J'ai expérimenté cela et ressenti votre douleur.

Ma solution était de:

arrêtez de traiter des tests comme construire un monolithe.

Rappelez-vous que lorsque vous avez écrit un ensemble de tests, disons 5, pour supprimer certaines fonctionnalités, vous n'avez pas besoin de garder tous ces tests , surtout lorsque cela fait partie de quelque chose d'autre.

Par exemple j'ai souvent:

  • test de niveau bas 1
  • code pour le rencontrer
  • test de niveau bas 2
  • code pour le rencontrer
  • test de niveau bas 3
  • code pour le rencontrer
  • test de niveau bas 4
  • code pour le rencontrer
  • test de niveau bas 5
  • code pour le rencontrer

alors j'ai

  • test de niveau bas 1
  • test de niveau bas 2
  • test de niveau bas 3
  • test de niveau bas 4
  • test de niveau bas 5

Toutefois, si j’ajoute maintenant une ou plusieurs fonctions de niveau supérieur appelées ainsi, comportant de nombreux tests, je pourrais peut- être maintenant réduire ces tests de bas niveau à simplement:

  • test de niveau bas 1
  • test de niveau bas 5

Le diable est dans les détails et la capacité de le faire dépendra des circonstances.

Michael Durrant
la source
-2

Le soleil tourne-t-il autour de la terre ou la terre autour du soleil? Selon Einstein, la réponse est oui, les deux modèles ne différant que par leur point de vue, de même que l'encapsulation et le développement piloté par les tests ne sont en conflit que parce que nous pensons qu'ils le sont. Nous sommes assis ici, comme Galilée et le pape, à nous lancer des injures: imbécile, ne vois-tu pas que les méthodes privées ont aussi besoin d'être testées; hérétique, ne cassez pas l'encapsulation! De même, lorsque nous reconnaissons que la vérité est plus grande que nous ne le pensions, pouvons-nous essayer quelque chose comme encapsuler les tests pour les interfaces privées afin que les tests pour les interfaces publiques ne cassent pas l'encapsulation.

Essayez ceci: ajoutez deux méthodes, l'une sans entrée, mais justs renvoie le nombre de tests privés et l'autre prenant un numéro de test en paramètre et renvoyant succès / échec.

Hildred
la source
1
Les insultes choisies ont été utilisées par Galilée et Le Pape, et non par aucune réponse à cette question.
hildred