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

modérateur Note: il y a déjà 39 réponses postées ici (certaines ont été supprimées). avant de poster votre réponse , demandez-vous si vous pouvez ajouter quelque chose de significatif à la discussion. Vous répétez sûrement ce que quelqu'un d'autre a déjà dit.


j'ai parfois besoin de faire un privé méthode dans un public de classe juste pour écrire quelques tests unitaires pour elle.

habituellement ce serait parce que la méthode contient la logique partagée entre d'autres méthodes dans la classe et il est plus pratique de tester la logique seule, ou une autre raison pourrait être possible que je veux tester la logique utilisée dans les fils synchrones sans avoir à se soucier des problèmes de filetage.

est-ce que d'autres personnes se retrouvent à faire ça, parce que je n'aime pas vraiment le faire?? Personnellement, je pensez que les primes compensent les problèmes de rendre une méthode publique qui ne fournit pas vraiment tout service en dehors de la classe...

mise à JOUR

Merci pour les réponses tout le monde, semble avoir piqué l'intérêt des peuples. Je pense que le consensus général est que les tests devraient se faire 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 deux cas que j'ai mentionnés ci-dessus où je ferais ceci ci-dessus étaient des cas rares et je pensais que les avantages de le faire en valait la peine.

je peux cependant voir tout le monde dire que cela ne devrait jamais vraiment se produire. Et en y réfléchissant un peu plus, je pense que changer votre code pour s'adapter aux tests est une mauvaise idée - Après tout, je suppose que Tester est un outil de soutien d'une certaine manière et changer un système en "support d'un outil de soutien" si vous voulez, est une mauvaise pratique flagrante.

263
demandé sur displayName 2011-08-16 13:11:42

30 réponses

Note:

Cette réponse a été publiée à l'origine pour la question est-ce que le test unitaire seul est une bonne raison pour exposer des variables d'instance privée via getters? qui a été fusionné dans celui-ci, il peut donc être un tad spécifique à l'usecase présenté là.

de façon générale, je suis généralement favorable à une refonte du code de" production " pour le rendre plus facile à tester. Cependant, je ne pense pas que ce serait une bonne décision ici. Un bon test unitaire (habituellement) ne devrait pas se soucier des détails d'implémentation de la classe, seulement de son comportement visible. Au lieu d'exposer les empilements internes au test, vous pouvez tester que la classe renvoie les pages dans l'ordre auquel vous vous 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());
    }
}
181
répondu Mureinik 2017-05-23 12:18:02

personnellement, je préférerais tester en unité 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 en isolation, en Java vous pouvez utiliser Easymock / Powermock pour vous permettre de le faire.

il faut être pragmatique à ce sujet et vous devriez aussi être conscient des raisons pour lesquelles les choses sont difficiles à tester.

' écouter les tests ' - si c'est difficile à tester, est-ce que cela vous dit quelque chose au sujet de votre conception? Pourriez-vous préciser 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 D'héritage "

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

144
répondu blank 2013-11-08 09:23:05

comme d'autres l'ont dit, il est quelque peu suspect de tester des méthodes privées à l'unité; tester l'interface publique, pas les détails privés de mise en œuvre.

cela dit, la technique que j'utilise quand je veux tester une unité qui est privée en C# est de déclasser la protection d'accessibilité de privé à interne, et puis marquer l'ensemble d'essai de l'unité comme un montage ami en utilisant Internalvisibleto . Les tests unitaires de l'assemblée sera alors autorisé à traiter les intérieurs comme public, mais vous n'avez pas à vous soucier d'ajouter accidentellement à votre surface publique.

61
répondu Eric Lippert 2011-08-17 12:47:33

beaucoup de réponses suggèrent de tester seulement l'interface publique, mais IMHO c'est irréaliste - si une méthode fait quelque chose qui prend 5 étapes, vous voudrez tester ces cinq étapes séparément, pas tous ensemble. Pour ce faire, il faut tester les cinq méthodes, qui (autre que pour les essais) pourrait autrement être private .

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

Oui, cela va se traduire dans le fichier de et classe-la météorisation.

Oui, cela rend les spécificateurs public et private redondants.

Oui, c'est une plaie.

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


il y a certains qui soutiendraient que chacune de ces étapes devrait être sa propre classe , mais je ne suis pas d'accord - s'ils partagent tous l'état, il n'y a aucune raison de créer cinq classes distinctes où cinq méthodes suffiraient. Pire encore, cela se traduit par des gonflements de fichiers et de classes. Plus , il infecte L'API publique de votre module - toutes ces classes doivent nécessairement être public si vous voulez les tester à partir d'un autre module (soit cela, soit inclure le code de test dans le même module, ce qui signifie l'expédition du code de test avec votre produit) .

39
répondu BlueRaja - Danny Pflughoeft 2017-04-12 07:31:17

un test unitaire devrait tester le marché public, la seule façon dont une classe pourrait être utilisée dans d'autres parties du code. Une méthode privée est des détails d'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 modifications dans les cas de test.

26
répondu kan 2011-08-16 09:19:41

IMO, vous devriez écrire vos tests ne faisant pas des hypothèses profondes sur la façon dont votre classe mis en œuvre à l'intérieur. Vous voudrez probablement le remanier plus tard en utilisant un autre modèle interne mais en faisant toujours les mêmes garanties que la mise en œuvre précédente donne.

gardant cela à l'esprit, je vous suggère de vous concentrer sur les tests que votre contrat est toujours valable, peu importe la mise en œuvre interne que votre classe a actuellement. Test basé sur la propriété de vos API publiques.

19
répondu SimY4 2015-07-27 11:04:00

Que Diriez-vous de le faire package privé? Puis votre code de test peut le voir (et d'autres classes dans votre forfait), mais il est toujours caché de vos utilisateurs.

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

rendre une méthode publique est un grand engagement. Une fois que vous aurez fait cela, les gens seront en mesure de l'utiliser, et vous ne pouvez plus simplement les changer.

17
répondu Thilo 2011-08-16 09:26:15

Update : j'ai ajouté une réponse plus étendue et plus complète à cette question dans de nombreux autres endroits. C'est peut être trouvé sur mon blog .

si j'ai besoin de rendre quelque chose public pour le tester, cela indique généralement que le système à l'essai 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 un nouvelle classe, rendez-la Publique. Maintenant vous pouvez tester facilement, et vous suivez SRP. Votre autre classe doit simplement invoquer cette nouvelle classe via la composition.

méthodes de Prise de public/à l'aide de la langue des astuces telles que le marquage de code visible pour tester assembilies 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...
   }
}

Refactor ce par l'introduction d'un programme de validation de l'objet.

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

Maintenant, tout ce que nous avons à faire teste que le validateur est invoqué correctement. Le processus de validation proprement dit (la logique auparavant privée) peut être testé dans l'isolement pur. Il ne sera pas nécessaire de mettre en place un test complexe pour s'assurer que cette validation est réussie.

14
répondu Finglas 2015-07-30 18:28:49

quelques grandes réponses. Une chose que je n'ai pas vu mentionné est qu'avec le développement basé sur les tests (TDD), les méthodes privées sont créées pendant la phase de remaniement (regardez méthode D'extrait pour un exemple d'un modèle de remaniement), et devrait donc déjà avoir la couverture de test nécessaire. Si fait correctement( et bien sûr, vous allez obtenir un sac mélangé d'opinions quand il s'agit de la justesse), vous ne devriez pas avoir à vous soucier de faire une méthode privée public afin que vous puissiez le tester.

12
répondu csano 2011-08-16 17:56:59

pourquoi ne pas diviser l'algorithme de gestion de la cheminée en une classe utilitaire? La classe utility peut gérer les piles et fournir des accesseurs publics. Les tests unitaires de la STI peuvent être axés sur les détails de la mise en œuvre. Les tests profonds pour les classes algorithmiquement délicates sont très utiles pour rider les cas de bordures et assurer la couverture.

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

10
répondu Alfred Armstrong 2015-07-27 15:06:29

les méthodes privées sont habituellement utilisées comme méthodes" d'aide". 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 voulez les tester.

  • use réflexion
  • Donner les méthodes d'accès au package

alternativement vous pouvez créer une nouvelle classe avec la méthode helper comme une méthode publique si c'est une bonne assez de candidats pour une nouvelle classe.

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

9
répondu adamjmarkham 2011-08-16 09:21:33

en java, il y a aussi l'option de le faire paquet privé (c'est-à-dire sans le modificateur de visibilité). Si vos tests unitaires sont dans le même paquet que la classe testée, il devrait alors être en mesure de voir ces méthodes, et est un peu plus sûr que de rendre la méthode complètement publique.

8
répondu Tom Jefferys 2011-08-16 09:20:41

si vous utilisez C# vous pouvez rendre la méthode interne. Comme ça, on ne pollue pas L'API publique.

puis Ajouter attribut à dll

[assembly: Internalvisibleto ("MyTestAssembly")]

maintenant toutes les méthodes sont visibles dans votre projet Mytestassemblly. Peut-être pas parfait, mais mieux vaut rendre publique la méthode privée juste pour la tester.

6
répondu Piotr Perak 2011-08-16 18:34:32

utilisez 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 retourner ce que vous attendez dans les situations que vous pouvez anticiper.

6
répondu Daniel Alexiuc 2015-07-27 06:28:49

en termes de test unitaire, vous ne devriez certainement pas ajouter plus de méthodes; je pense que vous feriez mieux de faire un cas test juste à propos de votre first() méthode, qui serait appelé avant chaque test; alors vous pouvez appeler plusieurs fois le - next() , previous() et last() pour voir si les résultats correspondent à votre attente. Je suppose que si vous n'ajoutez pas plus de méthodes à votre classe (juste pour les tests), vous vous en tiendriez au principe de la "boîte noire" des tests;

6
répondu Ion 2015-07-27 06:41:17

je dirais que c'est une mauvaise idée car je ne suis pas sûr que vous obtenez des avantages et potentiellement des problèmes dans la ligne. 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 en créant un scénario artificiel que vous n'avez jamais eu l'intention de se produire.

en outre, en déclarant la méthode comme Publique, ce qui est à dire que dans six mois (après avoir oublié que la seule raison de faire un méthode publique est pour tester) que vous (ou si vous avez remis le projet) quelqu'un complètement différent ne va pas l'utiliser, conduisant à des conséquences potentiellement involontaires et/ou un cauchemar de maintenance.

5
répondu beny23 2011-08-16 09:18:30

voir D'abord si la méthode doit être extraite dans une autre classe et rendue publique. Si ce n'est pas le cas, protégez le paquet et en Java annotez avec @VisibleForTesting .

5
répondu Noel Yap 2011-08-16 13:57:46

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

  1. boîte Noire

    l'école de la boîte noire dit que la classe doit être considérée comme une boîte noire que personne ne peut voir la mise en œuvre à l'intérieur. La seule façon de tester ceci est par le biais de L'API publique -- tout comme les utilisateurs de la classe l'utiliseront.

  2. tests en boîte blanche.

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

Je ne peux vraiment pas prendre part à la discussion. J'ai juste pensé qu'il serait intéressant de savoir qu'il existe deux façons distinctes pour tester une classe (ou d'une bibliothèque ou autre).

5
répondu bengtb 2011-08-17 12:36:33

il y a en fait des situations où vous devriez faire cela (par exemple quand vous mettez en œuvre certains algorithmes complexes). Il suffit de le faire package-privé et ce sera suffisant. Mais dans la plupart des cas, vous avez probablement trop complexe classes qui nécessite l'affacturage logique dans d'autres classes.

4
répondu Stanislav Bashkyrtsev 2011-08-16 09:20:53

méthodes privées que vous voulez tester en isolement sont une indication qu'il ya un autre "concept" enfoui dans votre classe. Extraire ce" concept "à sa propre classe et le tester comme une"unité" séparée.

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

4
répondu Jordão 2011-08-17 18:53:43

j'ai tendance à être d'accord que les bonus de l'avoir testé unitaire emportent sur les problèmes de l'accroissement de la visibilité de certains de ses membres. Une légère amélioration est de le rendre protégé et virtuel, puis de le surpasser dans une classe test pour l'exposer.

Sinon, si c'est une fonctionnalité que vous voulez tester séparément, ne suggère-t-il pas un objet manquant de votre conception? Peut-être que tu pourrais le mettre dans une autre classe testable...alors votre classe existante ne fait que déléguer une instance de cette nouvelle classe.

2
répondu IanR 2011-08-16 09:17:11

je garde généralement les classes d'essai dans le même projet/assemblage que les classes à l'essai.

De cette façon, je n'ai besoin que de la visibilité internal pour rendre testables les fonctions/classes.

cela complique quelque peu votre processus de construction, qui doit filtrer les classes de test. J'y arrive en nommant toutes mes classes de test TestedClassTest et en utilisant un regex pour filtrer ces classes.

cela ne s'applique évidemment qu'aux C# / .NET partie de votre question

2
répondu yas4891 2011-08-16 09:21:13

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

parfois cette méthode est enveloppée dans un bloc ifdef (j'écris principalement en C++) de sorte qu'elle n'est pas compilée pour la publication. Mais il est souvent utile dans la version de fournir des méthodes de validation qui promènent les arbres d'objet du programme vérifiant des choses.

2
répondu Zan Lynx 2011-08-16 14:46:11

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

alors que L'API publique doit être testée, il est parfois pratique et raisonnable d'obtenir des choses qui ne seraient normalement pas publiques.

quand:

  • une classe est nettement moins lisible, d'une part, par la rupture en plusieurs classes,
  • juste pour le rendre plus testable,
  • et de fournir des tests de l'accès à l'intérieur ferait l'affaire

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

2
répondu Ed Staub 2011-08-17 01:48:23

non, parce qu'il y a de meilleures façons de dépecer ce chat.

certains harnais d'essai unitaires s'appuient sur des macros dans la définition de la classe, qui s'étendent automatiquement pour créer des crochets lorsqu'ils sont construits en mode d'essai. C très style, mais il fonctionne.

un idiome OO plus facile est de faire tout ce que vous voulez tester "protégé" plutôt que "privé". Le harnais d'essai hérite de la classe à l'essai, et peut alors accéder à tous les membres protégés.

Ou vous pouvez aller pour la "ami". Personnellement, c'est la fonctionnalité de C++ que j'aime le moins parce qu'elle casse les règles d'encapsulation, mais il se trouve que c'est nécessaire pour la façon dont C++ implémente certaines fonctionnalités, donc hey ho.

quoi qu'il en soit, si vous testez à l'unité, alors vous êtes tout aussi susceptible d'avoir besoin d'injecter des valeurs dans ces membres. Le texto de boîte blanche est parfaitement valide. Et que vraiment briserait votre encapsulation.

2
répondu Graham 2015-07-28 01:07:45

vous ne devriez jamais laisser vos tests dicter votre code. Je ne parle pas de TDD ou d'autres DDs, je veux dire, exactement ce que vous demandez. Votre application a-t-elle besoin que ces méthodes soient publiques? Si c'est le cas alors les tester. Si ce n'est pas le cas, alors ne les rendez pas publics uniquement pour des 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 une structure de classes pour répondre un objectif des tests).

à la place, vous devriez "tester plus haut". Tester la méthode qui appelle la méthode privée. Mais vos tests devraient tester vos besoins d'applications et non vos "décisions d'implémentation".

par exemple (Code bod 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" livres " à la place.

ne laissez jamais vos tests prendre des décisions de conception de code pour vous. Test que vous obtenir le résultat escompté, pas comment vous obtenez ce résultat.

2
répondu coteyr 2015-07-28 01:46:29

comme d'autres l'ont fait remarquer en détail dans leurs commentaires, les tests unitaires devraient être axés sur les IPA publics. Cependant, mis à part le pour et le contre et la justification, vous pouvez appeler les 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 l'autorise. Appeler des méthodes privées est quelque chose que le cadre de ressort emploie avec son ReflectionUtils (voir la méthode makeAccessible(Method) ).

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

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

et une classe d'exemple 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 B, imprimera Doing something private. si vous en avez vraiment besoin, la réflexion pourrait être utilisée dans les tests unitaires pour accéder aux méthodes d'instances privées.

1
répondu Dan Cruz 2011-08-16 16:34:11

je laisse habituellement ces méthodes comme protected et place le test de l'unité dans le même paquet (mais dans un autre projet ou dossier source), où ils peuvent accéder à toutes les méthodes protégées parce que le chargeur de classe les placera dans le même espace de nom.

1
répondu hiergiltdiestfu 2014-05-02 09:26:43

personnellement, j'ai les mêmes problèmes quand on teste des méthodes privées et c'est parce que certains des outils de test sont limités. Il n'est pas bon votre conception d'être piloté par des outils limités si ils ne répondent pas à votre besoin changer l'outil pas la conception. Parce que votre demande pour C# Je ne peux pas proposer de bons outils de test, mais pour Java il y a deux outils puissants: TestNG et PowerMock, et vous pouvez trouver des outils de test correspondants pour la plate-forme .NET

0
répondu Xoke 2011-08-16 09:37:08