Rendre publique une méthode privée pour la tester à l'unité… bonne idée?

301

Note du modérateur: Il y a déjà 39 réponses publiées ici (certaines ont été supprimées). Avant de publier votre réponse, demandez-vous si vous pouvez ou non ajouter quelque chose de significatif à la discussion. Vous répétez probablement ce que quelqu'un d'autre a déjà dit.


Je me trouve parfois obligé de rendre une méthode privée dans une classe publique juste pour écrire des tests unitaires pour elle.

Habituellement, cela serait dû au fait que la méthode contient une logique partagée entre d'autres méthodes de la classe et qu'il est plus ordonné de tester la logique seule, ou une autre raison pourrait être possible car je veux tester la logique utilisée dans les threads synchrones sans avoir à se soucier des problèmes de threading .

Est-ce que d'autres personnes se retrouvent à faire ça, parce que je n'aime pas vraiment le faire ?? Personnellement, je pense que les bonus l'emportent sur les problèmes de rendre publique une méthode qui ne fournit pas vraiment de service en dehors de la classe ...

METTRE À JOUR

Merci pour les réponses à tous, semble avoir éveillé l'intérêt des gens. Je pense que le consensus général est que les tests devraient se produire via l'API publique car c'est la seule façon dont une classe sera jamais utilisée, et je suis d'accord avec cela. Les quelques cas que j'ai mentionnés ci-dessus où je ferais cela ci-dessus étaient des cas rares et je pensais que les avantages de le faire en valaient la peine.

Je peux cependant voir tout le monde que cela ne devrait jamais vraiment arriver. Et en y réfléchissant un peu plus, je pense que changer votre code pour prendre en charge les tests est une mauvaise idée - après tout, je suppose que les tests sont en quelque sorte un outil de support et changer un système pour 'supporter un outil de support' si vous voulez, est flagrant mauvaise pratique.

jcvandan
la source
2
"changer un système pour 'soutenir un outil de support' si vous voulez, est une mauvaise pratique flagrante". Eh bien, oui, en quelque sorte. Mais OTOH si votre système ne fonctionne pas bien avec les outils établis, peut-être que quelque chose ne va pas avec le système.
Thilo
1
Comment est-ce que ce doublon de stackoverflow.com/questions/105007/do-you-test-private-method ?
Sanghyun Lee
1
Pas une réponse, mais vous pouvez toujours y accéder par réflexion.
Zano
33
Non, vous ne devez pas les exposer. Au lieu de cela, vous devez tester que votre classe se comporte comme elle le devrait, via son API publique. Et si exposer les internes est vraiment la seule option (ce dont je doute), alors vous devriez au moins protéger le package d'accesseurs, de sorte que seules les classes du même package (comme votre test devrait l'être) puissent y accéder.
JB Nizet
4
Oui, ça l'est. Il est parfaitement acceptable de donner une méthode package-private (par défaut) pour la rendre accessible à partir de tests unitaires. Des bibliothèques comme Guava fournissent même une @VisibileForTestingannotation pour créer de telles méthodes - je vous recommande de le faire pour que la raison pour laquelle la méthode n'est pas privatecorrectement documentée.
Boris the Spider

Réponses:

186

Remarque:
Cette réponse a été initialement publiée pour la question Les tests unitaires sont-ils à eux seuls une bonne raison d'exposer des variables d'instance privées via des getters? qui a été fusionné dans celui-ci, il peut donc être un peu spécifique au cas d'utilisation présenté ici.

En règle générale, je suis généralement favorable à la refactorisation du code de "production" pour le rendre plus facile à tester. Cependant, je ne pense pas que ce serait un bon appel ici. Un bon test unitaire (généralement) ne devrait pas se soucier des détails d'implémentation de la classe, seulement de son comportement visible. Au lieu d'exposer les piles internes au test, vous pouvez tester que la classe renvoie les pages dans l'ordre dans lequel vous vous y attendez après avoir appelé first()ou last().

Par exemple, considérez ce pseudo-code:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}
Mureinik
la source
8
Je suis entièrement d'accord avec votre réponse. De nombreux développeurs seraient tentés d'utiliser le modificateur d'accès par défaut et justifieraient que de bons frameworks open source utilisent cette stratégie, elle doit donc être correcte. Ce n'est pas parce que vous le pouvez que vous le devriez. +1
CKing
6
Bien que d'autres aient fourni des informations utiles, je dois dire que c'est la solution que je trouve la plus appropriée à ce problème. Il garde les tests complètement indépendants de la manière dont le comportement souhaité est atteint en interne. +10!
Gitahi Ng'ang'a du
Soit dit en passant, est-il normal que dans l'exemple fourni par @Mureinik ici, les méthodes de test finissent par couvrir plus que la méthode réelle testée? Par exemple, testFirst () appelle next (), ce qui n'est pas vraiment la méthode testée ici, first () l'est. Ne serait-il pas plus propre et plus clair de n'avoir qu'une seule méthode de test couvrant l'ensemble des 4 méthodes de navigation?
Gitahi Ng'ang'a
1
Bien que ce soit l'idéal, il y a des moments où vous devez vérifier qu'un composant a fait quelque chose de la bonne manière et pas seulement qu'il a réussi. Cela peut être plus difficile à tester en boîte noire. Parfois, vous devez tester en blanc un composant.
Peter Lawrey
1
Créer des getters juste pour des tests unitaires? Cela va certainement à l'encontre du grain de pensée OOP / Object dans la mesure où l'interface doit exposer le comportement, pas les données.
IntelliData
151

Personnellement, je préfère faire des tests unitaires en utilisant l'API publique et je ne rendrais certainement jamais la méthode privée publique juste pour la rendre facile à tester.

Si vous voulez vraiment tester la méthode privée de manière isolée, en Java, vous pouvez utiliser Easymock / Powermock pour vous permettre de le faire.

Vous devez être pragmatique à ce sujet et vous devez également être conscient des raisons pour lesquelles les choses sont difficiles à tester.

« Écoutez les tests » - si c'est difficile à tester, cela vous dit-il quelque chose sur votre conception? Pourriez-vous refactoriser où un test pour cette méthode serait trivial et facilement couvert par des tests via l'API publique?

Voici ce que Michael Feathers a à dire dans " Travailler efficacement avec le code hérité "

"Beaucoup de gens passent beaucoup de temps à essayer de comprendre comment contourner ce problème ... la vraie réponse est que si vous avez envie de tester une méthode privée, la méthode ne devrait pas être privée; si vous rendez la méthode publique vous dérange, les chances sont, c'est parce que cela fait partie d'une responsabilité distincte; il devrait être sur une autre classe. " [ Travailler efficacement avec Legacy Code (2005) par M. Feathers]

Vide
la source
5
+1 pour Easymock / Powermock et ne jamais rendre publique une méthode privée
Leonard Brünings
51
+1 Pour "Écoutez les tests". Si vous ressentez le besoin de tester une méthode privée, il est probable que la logique de cette méthode soit si complexe qu'elle devrait être dans une classe d'assistance distincte. Ensuite, vous pouvez tester la classe d'assistance.
Phil
2
«Écoutez les tests» semble être en baisse. Voici un lien en cache: webcache.googleusercontent.com/…
Jess Telford
1
Je suis d'accord avec @Phil (en particulier la référence du livre), mais je me retrouve souvent dans une situation poulet / œuf avec un code hérité. Je sais que cette méthode appartient à une autre classe, mais je ne suis pas à 100% à l'aise de refactoring sans tests en premier lieu.
Kevin
Je suis d'accord docteur. Si vous devez tester une méthode privée, elle devrait probablement être dans une autre classe. Vous pouvez noter que l'autre classe / classe d'assistance que la méthode privée est susceptible de devenir publique / protégée.
rupweb
67

Comme d'autres l'ont dit, il est quelque peu suspect de tester en bloc des méthodes privées; testez l'interface publique, pas les détails de l'implémentation privée.

Cela dit, la technique que j'utilise lorsque je veux tester un élément qui est privé en C # consiste à rétrograder la protection d'accessibilité de privée à interne, puis à marquer l'assembly de test unitaire en tant qu'assembly ami à l'aide d' InternalsVisibleTo . L'ensemble de tests unitaires sera alors autorisé à traiter les composants internes comme publics, mais vous n'avez pas à vous soucier d'ajouter accidentellement à votre surface publique.

Eric Lippert
la source
J'utilise également cette technique, mais généralement en dernier recours pour le code hérité. Si vous avez besoin de tester du code privé, il vous manque une classe / la classe sous test en fait trop. Extrayez ce code dans une nouvelle classe que vous pouvez tester isolément. Des astuces comme celle-ci devraient être utilisées rarement.
Finglas
Mais extraire une classe et tester uniquement cela signifie que vos tests perdent la connaissance que la classe d'origine utilise réellement le code extrait. Comment recommandez-vous de combler cette lacune dans vos tests? Et si la réponse est de simplement tester l'interface publique de la classe d'origine de manière à ce que la logique extraite soit utilisée, alors pourquoi s'embêter à l'extraire en premier lieu? (Je ne veux pas sembler conflictuel ici, btw - je me suis toujours demandé comment les gens équilibraient de tels compromis.)
Mal Ross
@mal j'aurais un test de collaboration. En d'autres termes, une maquette pour vérifier la nouvelle classe a été invoquée correctement. Voir ma réponse ci-dessous.
Finglas
Pourrait-on écrire une classe de test qui hérite de la classe avec une logique interne que vous souhaitez tester et fournir une méthode publique à utiliser pour tester la logique interne?
sholsinger
1
@sholsinger: en C #, une classe publique ne peut pas hériter d'une classe interne.
Eric Lippert
45

De nombreuses réponses suggèrent de tester uniquement l'interface publique, mais à mon humble avis, cela n'est pas réaliste - si une méthode fait quelque chose qui prend 5 étapes, vous voudrez tester ces cinq étapes séparément, pas toutes ensemble. Cela nécessite de tester les cinq méthodes, qui (autrement que pour les tests) pourraient autrement l'être private.

La manière habituelle de tester les méthodes "privées" est de donner à chaque classe sa propre interface et de créer les méthodes "privées" public, mais pas de les inclure dans l'interface. De cette façon, ils peuvent toujours être testés, mais ils ne gonflent pas l'interface.

Oui, cela entraînera un ballonnement de fichier et de classe.

Oui, cela rend les spécificateurs publicet privateredondants.

Oui, c'est une douleur dans le cul.

C'est, malheureusement, l'un des nombreux sacrifices que nous faisons pour rendre le code testable . Peut-être qu'un futur langage (ou même une future version de C # / Java) aura des fonctionnalités pour rendre la testabilité des classes et des modules plus pratique; mais en attendant, nous devons sauter à travers ces cerceaux.


Certains diront que chacune de ces étapes devrait être sa propre classe , mais je ne suis pas d'accord - si elles partagent toutes l'état, il n'y a aucune raison de créer cinq classes distinctes où cinq méthodes feraient l'affaire. Pire encore, cela entraîne un ballonnement de fichiers et de classes. De plus , il infecte l'API publique de votre module - toutes ces classes doivent nécessairement l'être publicsi vous voulez les tester à partir d'un autre module (que ce soit, ou inclure le code de test dans le même module, ce qui signifie expédier le code de test avec votre produit) .

BlueRaja - Danny Pflughoeft
la source
2
belle réponse pour la 1ère mi
cmcginty
7
+1: La meilleure réponse pour moi. Si un constructeur automobile ne teste pas une partie de sa voiture pour la même raison, je ne suis pas sûr que quiconque l'achèterait. Une partie importante du code doit être testée même si nous devons la changer en public.
Tae-Sung Shin
1
Très bonne réponse. Vous avez mon vote chaleureux. J'ai ajouté une réponse. Cela peut vous intéresser.
davidxxx
29

Un test unitaire devrait tester le marché public, le seul moyen d'utiliser une classe dans d'autres parties du code. Une méthode privée est les détails de l'implémentation, vous ne devriez pas la tester, dans la mesure où l'API publique fonctionne correctement, l'implémentation n'a pas d'importance et pourrait être modifiée sans changements dans les cas de test.

kan
la source
+1, et une réponse
concrète
Afair, La rare situation dans laquelle j'ai jamais décidé d'utiliser des éléments privés dans un cas de test était lorsque je vérifiais l'exactitude d'un objet - s'il avait fermé tous les gestionnaires JNI. De toute évidence, il n'est pas exposé en tant qu'API publique, mais si cela ne se produit pas, une fuite de mémoire se produit. Mais drôle, plus tard, je m'en suis débarrassé aussi après avoir introduit un détecteur de fuite de mémoire dans le cadre de l'API publique.
kan
11
C'est une logique imparfaite. Le point de test unitaire est de détecter les erreurs le plus tôt possible. En ne testant pas les méthodes privées, vous laissez des lacunes dans vos tests qui ne seront découvertes que bien plus tard. Dans le cas où la méthode privée est complexe et difficile à mettre en œuvre par une interface publique, elle doit être testée unitaire.
cmcginty
6
@Casey: si la méthode privée n'est pas exercée par une interface publique, pourquoi est-elle là?
Thilo
2
@DaSilva_Ireland: Il sera difficile de maintenir des cas de test à l'avenir. Le seul vrai cas d'utilisation de classe est via la méthode publique. C'est-à-dire que tout autre code pourrait utiliser la classe via l'API publique uniquement. Donc, si vous voulez changer l'implémentation et l'échec du cas de test de la méthode privée, cela ne signifie pas que vous faites quelque chose de mal, de plus, si vous testez uniquement en privé, mais pas en public, cela ne couvrira pas certains bugs potentiels . Idéalement, un cas de test devrait couvrir tous les cas possibles et uniquement les cas d'utilisation réelle de la classe.
kan
22

OMI, vous devriez écrire vos tests sans faire d'hypothèses profondes sur la façon dont votre classe a été implémentée à l'intérieur. Vous voudrez probablement le refactoriser plus tard en utilisant un autre modèle interne mais en offrant toujours les mêmes garanties que la mise en œuvre précédente.

Gardant cela à l'esprit, je vous suggère de vous concentrer sur le test de la validité de votre contrat, quelle que soit l'implémentation interne de votre classe. Test basé sur les propriétés de vos API publiques.

SimY4
la source
6
Je suis d'accord que les tests unitaires qui n'ont pas besoin d'être réécrits après une refactorisation du code sont meilleurs. Et pas seulement à cause du temps que vous passeriez à réécrire les tests unitaires. Une raison beaucoup plus importante est que je considère que le but le plus important des tests unitaires est que votre code se comporte toujours correctement après une refactorisation. Si vous devez réécrire tous vos tests unitaires après une refactorisation, vous perdez cet avantage des tests unitaires.
kasperd
20

Que diriez-vous de le rendre privé? Ensuite, votre code de test peut le voir (ainsi que d'autres classes de votre package), mais il est toujours caché à vos utilisateurs.

Mais vraiment, vous ne devriez pas tester des méthodes privées. Ce sont des détails de mise en œuvre et ne font pas partie du contrat. Tout ce qu'ils font devrait être couvert en appelant les méthodes publiques (s'ils contiennent du code qui n'est pas exercé par les méthodes publiques, cela devrait aller). Si le code privé est trop complexe, la classe fait probablement trop de choses et manque de refactoring.

Rendre une méthode publique est un grand engagement. Une fois que vous aurez fait cela, les gens pourront l'utiliser et vous ne pourrez plus les changer.

Thilo
la source
+1 si vous avez besoin de tester vos cours privés, il vous manque probablement une classe
jk.
+1 pour la classe manquante. Heureux de voir que les autres ne sautent pas tout de suite dans le mouvement "Mark it internal".
Finglas
Garçon, j'ai dû descendre un long chemin pour trouver la réponse privée du paquet.
Bill K
17

Mise à jour : j'ai ajouté une réponse plus étendue et plus complète à cette question dans de nombreux autres endroits. Ceci est disponible sur mon blog .

Si jamais je dois rendre quelque chose public pour le tester, cela indique généralement que le système testé ne suit pas le principe de responsabilité unique . Il y a donc une classe manquante qui devrait être introduite. Après avoir extrait le code dans une nouvelle classe, rendez-le public. Vous pouvez maintenant tester facilement et vous suivez SRP. Votre autre classe n'a qu'à invoquer cette nouvelle classe via la composition.

Rendre les méthodes publiques / utiliser des astuces langauge telles que le marquage du code comme visible pour tester les assemblys devrait toujours être un dernier recours.

Par exemple:

public class SystemUnderTest
{
   public void DoStuff()
   {
      // Blah
      // Call Validate()
   }

   private void Validate()
   {
      // Several lines of complex code...
   }
}

Refactorisez ceci en introduisant un objet validateur.

public class SystemUnderTest
{
    public void DoStuff()
    {
       // Blah
       validator.Invoke(..)
    }
}

Il ne nous reste plus qu'à tester que le validateur est correctement appelé. Le processus réel de validation (la logique précédemment privée) peut être testé dans l'isolement pur. Il ne sera pas nécessaire de mettre en place un test complexe pour garantir le succès de cette validation.

Finglas
la source
1
Personnellement, je pense que SRP peut être poussé trop loin, par exemple si j'écris un service de compte qui traite de la mise à jour / création / suppression de comptes dans une application, me dites-vous que vous devez créer une classe distincte pour chaque opération? Et qu'en est-il de la validation par exemple, cela vaut-il vraiment la peine d'extraire la logique de validation vers une autre classe alors qu'elle ne sera jamais utilisée ailleurs? imo qui rend le code moins lisible, moins concis et emballe votre application avec des classes superflues!
jcvandan
1
Tout dépend du scénario. La définition d'une personne de «faire une chose» peut être différente de celle des autres. Dans ce cas pour vous-même, le code privé que vous souhaitez tester est clairement complexe / difficile à configurer. Le fait même que vous souhaitiez le tester prouve qu'il en fait trop. Donc oui, avoir un nouvel objet sera idéal.
Finglas
1
Quant à rendre votre code moins lisible, je ne suis pas du tout d'accord. Il est plus facile de lire avoir une classe pour la validation que d'avoir la validation incorporée dans l'autre objet. Vous avez toujours la même quantité de code, il n'est tout simplement pas imbriqué dans une grande classe. Je préfère avoir plusieurs classes qui font très bien une chose, plutôt qu'une grande classe.
Finglas
4
@dormisher, en ce qui concerne le service de votre compte, considérez le SRP comme une "raison unique de changer". Si vos opérations CRUD changeaient naturellement ensemble, alors elles correspondraient au SRP. Normalement, vous les modifieriez car le schéma de la base de données pourrait changer, ce qui affecterait naturellement l'insertion et la mise à jour (mais peut-être pas toujours la suppression).
Anthony Pegram
@finglas ce que vous en pensez: stackoverflow.com/questions/29354729/… . Je n'ai pas envie de tester la méthode privée, alors peut-être que je ne devrais pas la séparer en classe, mais elle est utilisée dans 2 autres méthodes publiques, donc je finirais par tester deux fois la même logique.
Rodrigo Ruiz
13

De bonnes réponses. Une chose que je n'ai pas vue mentionnée est qu'avec le développement piloté par les tests (TDD), des méthodes privées sont créées pendant la phase de refactoring (regardez Extract Method pour un exemple de modèle de refactoring), et devraient donc déjà avoir la couverture de test nécessaire . Si cela est fait correctement (et bien sûr, vous obtiendrez un mélange d'opinions en matière d'exactitude), vous ne devriez pas avoir à vous soucier de devoir rendre publique une méthode privée juste pour pouvoir la tester.

csano
la source
11

Pourquoi ne pas diviser l'algorithme de gestion de pile en une classe utilitaire? La classe d'utilité peut gérer les piles et fournir des accesseurs publics. Ses tests unitaires peuvent être axés sur les détails de mise en œuvre. Des tests approfondis pour les classes algorithmiquement délicates sont très utiles pour éliminer les cas marginaux et assurer la couverture.

Ensuite, votre classe actuelle peut déléguer proprement à la classe utilitaire sans exposer aucun détail d'implémentation. Ses tests porteront sur l'exigence de pagination comme d'autres l'ont recommandé.

Alfred Armstrong
la source
10

En java, il est également possible de le rendre privé (c'est-à-dire en laissant le modificateur de visibilité). Si vos tests unitaires sont dans le même package que la classe testée, ils devraient alors pouvoir voir ces méthodes, et sont un peu plus sûrs que de rendre la méthode complètement publique.

Tom Jefferys
la source
10

Les méthodes privées sont généralement utilisées comme méthodes "auxiliaires". Par conséquent, ils ne renvoient que des valeurs de base et ne fonctionnent jamais sur des instances spécifiques d'objets.

Vous avez quelques options si vous souhaitez les tester.

  • Utiliser la réflexion
  • Donner accès au package de méthodes

Vous pouvez également créer une nouvelle classe avec la méthode d'assistance en tant que méthode publique si elle est un assez bon candidat pour une nouvelle classe.

Il y a un très bon article ici à ce sujet.

adamjmarkham
la source
1
Bon commentaire. C'était juste une option, peut-être une mauvaise.
adamjmarkham
@Finglas Pourquoi tester du code privé en utilisant la réflexion est-il une mauvaise idée?
Anshul
Les méthodes privées @Anshul sont des détails d'implémentation et non un comportement. En d'autres termes, ils changent. Les noms changent. Les paramètres changent. Le contenu change. Cela signifie que ces tests se cassent tout le temps. En revanche, les méthodes publiques sont beaucoup plus stables en termes d'API et sont donc plus résistantes. À condition que vos tests sur les méthodes publiques vérifient le comportement et non les détails d'implémentation, vous devriez être bon.
Finglas
1
@Finglas Pourquoi ne voudriez-vous pas tester les détails de l'implémentation? C'est du code qui doit fonctionner, et s'il change plus souvent, vous vous attendez à ce qu'il se casse plus souvent, ce qui est une raison de plus de le tester que l'API publique. Une fois que votre base de code est stable, disons que dans le futur, quelqu'un arrive et change une méthode privée qui rompt le fonctionnement interne de votre classe. Un test qui teste les internes de votre classe leur dirait immédiatement qu'ils ont cassé quelque chose. Oui, cela rendrait la maintenance de vos tests plus difficile, mais c'est ce que vous attendez des tests de couverture complète.
Anshul
1
Les détails d'implémentation de @Finglas Testing ne sont pas faux. C'est votre position à ce sujet, mais c'est un sujet largement débattu. Le test des composants internes vous offre quelques avantages, même s'ils peuvent ne pas être requis pour une couverture complète. Vos tests sont plus ciblés car vous testez directement les méthodes privées plutôt que de passer par l'API publique, ce qui peut confondre votre test. Les tests négatifs sont plus faciles car vous pouvez invalider manuellement les membres privés et vous assurer que l'API publique renvoie les erreurs appropriées en réponse à l'état interne incohérent. Il y a plus d'exemples.
Anshul
9

Si vous utilisez C #, vous pouvez rendre la méthode interne. De cette façon, vous ne polluez pas l'API publique.

Ajoutez ensuite un attribut à la DLL

[assembly: InternalsVisibleTo ("MyTestAssembly")]

Maintenant, toutes les méthodes sont visibles dans votre projet MyTestAssembly. Peut-être pas parfait, mais mieux que de rendre publique une méthode privée juste pour la tester.

Piotr Perak
la source
7

Utilisez la réflexion pour accéder aux variables privées si vous en avez besoin.

Mais vraiment, vous ne vous souciez pas de l'état interne de la classe, vous voulez juste tester que les méthodes publiques retournent ce que vous attendez dans les situations que vous pouvez anticiper.

Daniel Alexiuc
la source
Que feriez-vous avec une méthode nulle?
IntelliData
@IntelliData Utilisez la réflexion pour accéder aux variables privées si vous en avez besoin.
Daniel Alexiuc
6

Je dirais que c'est une mauvaise idée car je ne sais pas si vous obtenez des avantages et potentiellement des problèmes en fin de compte. Si vous modifiez le contrat d'un appel, juste pour tester une méthode privée, vous ne testez pas la classe dans la façon dont elle serait utilisée, mais créez un scénario artificiel que vous n'avez jamais voulu voir se produire.

De plus, en déclarant la méthode publique, que dire en six mois (après avoir oublié que la seule raison de rendre une méthode publique est de tester) que vous (ou si vous avez remis le projet) quelqu'un de complètement différent a gagné ne l'utilisez pas, ce qui entraînerait des conséquences potentiellement imprévues et / ou un cauchemar de maintenance.

beny23
la source
1
qu'en est-il si la classe est l'implémentation d'un contrat de service et qu'elle n'est utilisée que via son interface - de cette façon, même si la méthode privée a été rendue publique, elle ne sera pas appelée de toute façon car elle n'est pas visible
jcvandan
Je voudrais toujours tester la classe à l'aide de l'API publique (interface), car sinon, si vous refactorisez la classe pour diviser la méthode interne, vous devrez changer les tests, ce qui signifie que vous devrez dépenser un peu effort pour garantir que les nouveaux tests fonctionnent de la même manière que les anciens tests.
beny23
De plus, si le seul moyen de garantir que la couverture de test inclut chaque cas de périphérie à l'intérieur d'une méthode privée est de l'appeler directement, il peut être indicatif que la complexité de la classe justifie une refactorisation pour la simplifier.
beny23
@dormisher - Si la classe n'est utilisée que via l'interface, il vous suffit de tester que les méthodes de l'interface publique se comportent correctement. Si les méthodes publiques font ce qu'elles sont censées faire, pourquoi vous souciez-vous des méthodes privées?
Andrzej Doyle
@Andrzej - Je suppose que je ne devrais pas vraiment mais il y a des situations où je pense que cela pourrait être valide, par exemple une méthode privée qui valide l'état du modèle, cela peut valoir la peine de tester une méthode comme celle-ci - mais une méthode comme celle-ci devrait être testable via l'interface publique quand même
jcvandan
6

en termes de tests unitaires, vous ne devez certainement pas ajouter plus de méthodes; Je crois que vous feriez mieux de faire un cas de test à peu près votre first()méthode, qui serait appelée avant chaque test; alors vous pouvez appeler plusieurs fois - next(), previous()et last()de voir si les résultats correspondent à vos attentes. Je suppose que si vous n'ajoutez pas plus de méthodes à votre classe (juste à des fins de test), vous vous en tiendriez au principe de la "boîte noire" du test;

Johny
la source
5

Vérifiez d'abord si la méthode doit être extraite dans une autre classe et rendue publique. Si ce n'est pas le cas, faites-le protéger le package et annotez en Java avec @VisibleForTesting .

Noel Yap
la source
5

Dans votre mise à jour, vous dites qu'il est bon de simplement tester en utilisant l'API publique. Il y a en fait deux écoles ici.

  1. Test de boîte noire

    L'école de la boîte noire dit que la classe doit être considérée comme une boîte noire dont personne ne peut voir la mise en œuvre à l'intérieur. La seule façon de tester cela est via l'API publique - tout comme l'utilisateur de la classe l'utilisera.

  2. test de boîte blanche.

    L'école de la boîte blanche pense naturellement utiliser les connaissances sur la mise en œuvre de la classe et ensuite tester la classe pour savoir qu'elle fonctionne comme il se doit.

Je ne peux vraiment pas prendre parti dans la discussion. Je pensais juste qu'il serait intéressant de savoir qu'il existe deux façons distinctes de tester une classe (ou une bibliothèque ou autre).

bengtb
la source
4

Il y a en fait des situations où vous devriez le faire (par exemple lorsque vous implémentez des algorithmes complexes). Il suffit de le faire en paquet privé et ce sera suffisant. Mais dans la plupart des cas, vous avez probablement des classes trop complexes qui nécessitent la factorisation de la logique dans d'autres classes.

Stanislav Bashkyrtsev
la source
4

Les méthodes privées que vous souhaitez tester isolément indiquent qu'il existe un autre "concept" enfoui dans votre classe. Extrayez ce "concept" dans sa propre classe et testez-le comme une "unité" distincte.

Jetez un oeil à cette vidéo pour une prise vraiment intéressante sur le sujet.

Jordão
la source
4

Vous ne devriez jamais laisser vos tests dicter votre code. Je ne parle pas de TDD ou d'autres DD, je veux dire, exactement ce que vous demandez. Votre application a-t-elle besoin de ces méthodes pour être publique? Si c'est le cas, testez-les. Si ce n'est pas le cas, ne les rendez pas publics uniquement pour les tests. Idem pour les variables et autres. Laissez les besoins de votre application dicter le code et laissez vos tests tester que le besoin est satisfait. (Encore une fois, je ne veux pas dire tester d'abord ou pas, je veux dire changer la structure des classes pour atteindre un objectif de test).

Au lieu de cela, vous devriez "tester plus haut". Testez la méthode qui appelle la méthode privée. Mais vos tests doivent tester les besoins de vos applications et non vos "décisions d'implémentation".

Par exemple (bod pseudo code ici);

   public int books(int a) {
     return add(a, 2);
   }
   private int add(int a, int b) {
     return a+b;
   } 

Il n'y a aucune raison de tester "ajouter", vous pouvez tester les "livres" à la place.

Ne laissez jamais vos tests prendre des décisions de conception de code pour vous. Testez que vous obtenez le résultat attendu, pas comment vous obtenez ce résultat.

coteyr
la source
1
"Ne laissez jamais vos tests prendre des décisions de conception de code pour vous" - je dois être en désaccord. Être difficile à tester est une odeur de code. Si vous ne pouvez pas le tester, comment savez-vous qu'il n'est pas cassé?
Alfred Armstrong
Par souci de clarté, je conviens que vous ne devez pas modifier la conception de l'API externe d'une bibliothèque, par exemple. Mais vous devrez peut-être refactoriser pour le rendre plus testable.
Alfred Armstrong
J'ai trouvé que si vous avez besoin de refactoriser pour tester, ce n'est pas l'odeur de code dont vous devriez vous inquiéter. Vous avez autre chose de mal. Bien sûr, c'est une expérience personnelle, et nous pourrions probablement tous les deux faire valoir des données solides dans les deux sens. Je n'ai simplement jamais eu de "difficile à tester" qui n'était pas "un mauvais design" au départ.
coteyr
Cette solution est idéale pour une méthode comme les livres qui renvoie une valeur - vous pouvez vérifier le type de retour dans une assertion ; mais comment feriez-vous pour vérifier une méthode void ?
IntelliData
les méthodes void font quelque chose ou elles n'ont pas besoin d'exister. Vérifiez ce qu'ils font,
coteyr
2

J'ai tendance à convenir que les bonus liés à son test unitaire l'emportent sur les problèmes liés à l'augmentation de la visibilité de certains membres. Une légère amélioration consiste à le rendre protégé et virtuel, puis à le remplacer dans une classe de test pour l'exposer.

Alternativement, si c'est une fonctionnalité que vous souhaitez tester séparément, cela ne suggère-t-il pas un objet manquant dans votre conception? Peut-être pourriez-vous le mettre dans une classe testable séparée ... alors votre classe existante délègue simplement à une instance de cette nouvelle classe.

IanR
la source
2

Je garde généralement les classes de test dans le même projet / assemblage que les classes sous test.
De cette façon, je n'ai besoin que de internalvisibilité pour rendre les fonctions / classes testables.

Cela complique quelque peu votre processus de construction, qui doit filtrer les classes de test. J'y parviens en nommant toutes mes classes de test TestedClassTestet en utilisant une expression régulière pour filtrer ces classes.

Bien sûr, cela ne s'applique qu'à la partie C # / .NET de votre question

yas4891
la source
2

Je vais souvent ajouter une méthode appelée quelque chose comme validate, verify, check, etc., à une classe afin qu'il puisse être appelé à tester l'état interne d'un objet.

Parfois, cette méthode est enveloppée dans un bloc ifdef (j'écris principalement en C ++) afin qu'elle ne soit pas compilée pour publication. Mais il est souvent utile dans la version de fournir des méthodes de validation qui parcourent les arbres d'objets du programme en vérifiant les choses.

Zan Lynx
la source
2

La goyave a une annotation @VisibleForTesting pour les méthodes de marquage qui ont une portée élargie (package ou public) qu'elles auraient autrement. J'utilise une annotation @Private pour la même chose.

Bien que l'API publique doive être testée, il est parfois pratique et sensé d'accéder à des choses qui ne seraient normalement pas publiques.

Quand:

  • une classe est rendue nettement moins lisible, au total, en la divisant en plusieurs classes,
  • juste pour le rendre plus testable,
  • et fournir un accès de test aux entrailles ferait l'affaire

il semble que la religion l'emporte sur l'ingénierie.

Ed Staub
la source
2

Je laisse généralement ces méthodes en protectedplace et place le test unitaire dans le même package (mais dans un autre projet ou dossier source), où elles peuvent accéder à toutes les méthodes protégées car le chargeur de classe les placera dans le même espace de noms.

hiergiltdiestfu
la source
2

Non, car il existe de meilleures façons d'écorcher ce chat.

Certains faisceaux de tests unitaires s'appuient sur des macros dans la définition de classe qui se développent automatiquement pour créer des crochets lorsqu'elles sont construites en mode test. Style très C, mais ça marche.

Un idiome OO plus simple consiste à rendre tout ce que vous voulez tester «protégé» plutôt que «privé». Le faisceau de test hérite de la classe en cours de test et peut ensuite accéder à tous les membres protégés.

Ou vous optez pour l'option "ami". Personnellement, c'est la fonctionnalité de C ++ que j'aime le moins car elle enfreint les règles d'encapsulation, mais elle s'avère nécessaire pour la façon dont C ++ implémente certaines fonctionnalités, alors hé ho.

Quoi qu'il en soit, si vous effectuez des tests unitaires, vous devrez tout aussi probablement injecter des valeurs dans ces membres. Le texte de la boîte blanche est parfaitement valide. Et ce vraiment serait briser votre encapsulation.

Graham
la source
2

Dans .Net, il existe une classe spéciale appelée PrivateObjectspécialement daignée pour vous permettre d'accéder aux méthodes privées d'une classe.

En savoir plus sur MSDN ou ici sur Stack Overflow

(Je me demande si personne ne l'a mentionné jusqu'à présent.)

Il y a des situations où cela ne suffit pas, auquel cas vous devrez utiliser la réflexion.

J'adhérerais toujours à la recommandation générale de ne pas tester les méthodes privées, mais comme d'habitude, il y a toujours des exceptions.

yoel halb
la source
1

Comme cela est largement noté par les commentaires des autres, les tests unitaires devraient se concentrer sur l'API publique. Cependant, mis à part le pour / le contre et la justification, vous pouvez appeler des méthodes privées dans un test unitaire en utilisant la réflexion. Vous devez bien sûr vous assurer que votre sécurité JRE le permet. L'appel de méthodes privées est quelque chose que Spring Framework utilise avec ses ReflectionUtils (voir la makeAccessible(Method)méthode).

Voici un petit exemple de classe avec une méthode d'instance privée.

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

Et un exemple de classe qui exécute la méthode d'instance privée.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

L'exécution de B, imprimera Doing something private. Si vous en avez vraiment besoin, la réflexion peut être utilisée dans les tests unitaires pour accéder aux méthodes d'instance privées.

Dan Cruz
la source
0

Personnellement, j'ai les mêmes problèmes lorsque je teste des méthodes privées et c'est parce que certains des outils de test sont limités. Ce n'est pas bon que votre conception soit guidée par des outils limités s'ils ne répondent pas à votre besoin, changez l'outil et non la conception. Parce que votre demande de C # je ne peux pas proposer de bons outils de test, mais pour Java, il existe deux outils puissants: TestNG et PowerMock, et vous pouvez trouver des outils de test correspondants pour la plate-forme .NET

Xoke
la source