Comment tester une fonction privée ou une classe qui a des méthodes, des champs ou des classes internes privées?


2729

Comment puis-je tester unitaire (en utilisant xUnit) une classe qui a des méthodes privées internes, des champs ou des classes imbriquées? Ou une fonction rendue privée par un lien interne ( staticen C / C ++) ou se trouvant dans un espace de noms privé ( anonyme )?

Il semble mauvais de changer le modificateur d'accès pour une méthode ou une fonction juste pour pouvoir exécuter un test.


257
La meilleure façon de tester une méthode privée n'est pas de la tester directement
Surya


383
Je ne suis pas d'accord. Une méthode (publique) longue ou difficile à comprendre doit être refondue. Ce serait une folie de ne pas tester les petites méthodes (privées) que vous obtenez au lieu de la seule publique.
Michael Piefel,

181
Ne tester aucune méthode simplement parce que sa visibilité est stupide. Même le test unitaire devrait concerner le plus petit morceau de code, et si vous testez uniquement des méthodes publiques, vous ne savez jamais où l'erreur se produit, cette méthode ou une autre.
Dainius

126
Vous devez tester la fonctionnalité de classe , pas son implémentation . Vous voulez tester les méthodes privées? Testez les méthodes publiques qui les appellent. Si la fonctionnalité offerte par la classe est testée minutieusement, les éléments internes de celle-ci se sont révélés corrects et fiables; vous n'avez pas besoin de tester les conditions internes. Les tests doivent maintenir le découplage des classes testées.
Calimar41

Réponses:


1640

Mise à jour:

Une dizaine d'années plus tard, la meilleure façon de tester une méthode privée ou tout membre inaccessible est via @Jailbreakle framework Manifold .

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

De cette façon, votre code reste sûr et lisible. Pas de compromis de conception, pas de méthodes et de champs de surexposition pour les besoins des tests.

Si vous disposez d'une application Java héritée et que vous n'êtes pas autorisé à modifier la visibilité de vos méthodes, la meilleure façon de tester des méthodes privées consiste à utiliser la réflexion .

En interne, nous utilisons des assistants pour obtenir / définir privateet les private staticvariables ainsi que pour appeler privateet les private staticméthodes. Les modèles suivants vous permettront de faire à peu près tout ce qui concerne les méthodes et les champs privés. Bien sûr, vous ne pouvez pas modifier les private static finalvariables par réflexion.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

Et pour les champs:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Remarques:
1. TargetClass.getDeclaredMethod(methodName, argClasses)vous permet d'examiner les privateméthodes. La même chose s'applique pour getDeclaredField.
2. Le setAccessible(true)est requis pour jouer avec des soldats.


350
Utile si vous ne connaissez peut-être pas l'API, mais si vous devez tester des méthodes privées de cette manière, votre conception a quelque chose. Comme une autre affiche dit que les tests unitaires devraient tester le contrat de la classe: si le contrat est trop large et instancie trop le système, alors la conception doit être abordée.
andygavin le

21
Très utile. En utilisant cela, il est important de garder à l'esprit que cela échouerait gravement si les tests étaient exécutés après l'obfuscation.
Rick Minerich

20
L'exemple de code n'a pas fonctionné pour moi, mais cela a rendu les choses plus claires: java2s.com/Tutorial/Java/0125__Reflection/…
Rob

35
Beaucoup mieux que d'utiliser directement la réflexion serait d'utiliser une bibliothèque pour cela, comme Powermock .
Michael Piefel,

25
C'est pourquoi la conception pilotée par les tests est utile. Il vous aide à comprendre ce qui doit être exposé afin de valider le comportement.
Thorbjørn Ravn Andersen

621

La meilleure façon de tester une méthode privée est via une autre méthode publique. Si cela ne peut pas être fait, l'une des conditions suivantes est remplie:

  1. La méthode privée est le code mort
  2. Il y a une odeur de design près de la classe que vous testez
  3. La méthode que vous essayez de tester ne doit pas être privée

6
De plus, nous n'obtenons jamais une couverture de code à 100% de toute façon, alors pourquoi ne pas concentrer votre temps à faire des tests de qualité sur les méthodes que les clients utiliseront réellement directement.
grinch

30
@grinch Spot on. La seule chose que vous gagneriez à tester des méthodes privées est le débogage des informations, et c'est à cela que servent les débogueurs. Si vos tests du contrat de classe ont une couverture complète, vous disposez de toutes les informations dont vous avez besoin. Les méthodes privées sont un détail d' implémentation . Si vous les testez, vous devrez changer vos tests chaque fois que votre implémentation change, même si le contrat ne change pas. Dans le cycle de vie complet du logiciel, cela coûtera probablement beaucoup plus que les avantages qu'il offre.
Erik Madsen

9
@AlexWien vous avez raison. La couverture n'était pas le bon terme. Ce que j'aurais dû dire était "si vos tests du contrat de classe couvrent toutes les entrées significatives et tous les états significatifs". Cela, vous le signalez à juste titre, peut s'avérer impossible à tester via l'interface publique du code, ou indirectement lorsque vous vous y référez. Si tel est le cas pour certains codes que vous testez, je serais porté à penser: (1) Le contrat est trop généreux (par exemple, trop de paramètres dans la méthode), ou (2) Le contrat est trop vague (par exemple, le comportement de la méthode varie considérablement selon l'état de la classe). Je considérerais les deux odeurs de conception.
Erik Madsen

16
Presque toutes les méthodes privées ne doivent pas être testées directement (pour réduire les coûts de maintenance). Exception: les méthodes scientifiques peuvent avoir des formes comme f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) où a, b, c, d, e et f sont des expressions horriblement compliquées mais autrement inutiles en dehors de cette formule unique. Certaines fonctions (pensez MD5) sont difficiles à inverser, il peut donc être difficile (NP-difficile) de choisir des paramètres qui couvriront entièrement l'espace de comportement. Il est plus facile de tester indépendamment a, b, c, d, e, f. Les rendre publics ou les paquets-mensonges qu'ils sont réutilisables. Solution: rendez-les privés, mais testez-les.
Éponyme

4
@ Éponyme, je suis un peu déchiré sur celui-ci. Le puriste orienté objet en moi dirait que vous devriez utiliser une approche orientée objet plutôt que des méthodes privées statiques, vous permettant de tester via des méthodes d'instance publique. Mais là encore, dans un cadre scientifique, la surcharge de mémoire est tout à fait susceptible d'être une préoccupation qui, selon moi, plaide pour l'approche statique.
Erik Madsen

323

Quand j'ai des méthodes privées dans une classe qui sont suffisamment compliquées pour que je ressens le besoin de tester directement les méthodes privées, c'est une odeur de code: ma classe est trop compliquée.

Mon approche habituelle pour résoudre ces problèmes est de révéler une nouvelle classe qui contient les bits intéressants. Souvent, cette méthode et les champs avec lesquels elle interagit, et peut-être qu'une ou deux autres méthodes peuvent être extraites dans une nouvelle classe.

La nouvelle classe expose ces méthodes comme «publiques», elles sont donc accessibles pour les tests unitaires. Les classes nouvelles et anciennes sont maintenant plus simples que la classe d'origine, ce qui est génial pour moi (j'ai besoin de garder les choses simples, sinon je me perds!).

Notez que je ne suggère pas que les gens créent des classes sans utiliser leur cerveau! Le point ici est d'utiliser les forces des tests unitaires pour vous aider à trouver de bonnes nouvelles classes.


24
La question était de savoir comment tester des méthodes privées. Vous dites que vous feriez une nouvelle classe pour cela (et ajouteriez beaucoup plus de complexité) et après vous proposez de ne pas créer de nouvelle classe. Alors, comment tester des méthodes privées?
Dainius

27
@Dainius: Je ne suggère pas de créer une nouvelle classe uniquement pour pouvoir tester cette méthode. Je suggère que l'écriture de tests puisse vous aider à améliorer votre conception: les bons designs sont faciles à tester.
Jay Bazuzi

13
Mais vous convenez qu'un bon OOD exposerait (rendrait public) uniquement les méthodes qui sont nécessaires pour que cette classe / cet objet fonctionne correctement? tous les autres doivent être privés / protecteurs. Ainsi, dans certaines méthodes privées, il y aura une certaine logique, et les tests de l'OMI sur ces méthodes ne feront qu'améliorer la qualité du logiciel. Bien sûr, je conviens que si un morceau de code est trop complexe, il doit être divisé en méthodes / classe distinctes.
Dainius

6
@Danius: L'ajout d'une nouvelle classe, dans la plupart des cas, réduit la complexité. Il suffit de le mesurer. La testabilité dans certains cas se bat contre OOD. L'approche moderne privilégie la testabilité.
AlexWien

5
C'est exactement comment utiliser les retours de vos tests pour piloter et améliorer votre design! Écoutez vos tests. s'ils sont difficiles à écrire, cela vous dit quelque chose d'important sur votre code!
pnschofield

289

J'ai utilisé la réflexion pour faire cela pour Java dans le passé, et à mon avis, c'était une grosse erreur.

À strictement parler, vous ne devez pas écrire des tests unitaires qui testent directement des méthodes privées. Ce que vous devriez tester, c'est le contrat public que la classe a avec d'autres objets; vous ne devez jamais tester directement les éléments internes d'un objet. Si un autre développeur souhaite apporter une petite modification interne à la classe, ce qui n'affecte pas le contrat public des classes, il / elle doit alors modifier votre test basé sur la réflexion pour s'assurer qu'il fonctionne. Si vous le faites à plusieurs reprises tout au long d'un projet, les tests unitaires cessent alors d'être une mesure utile de la santé du code et commencent à devenir un obstacle au développement et une gêne pour l'équipe de développement.

Ce que je recommande à la place, c'est d'utiliser un outil de couverture de code tel que Cobertura, pour garantir que les tests unitaires que vous écrivez fournissent une couverture décente du code dans des méthodes privées. De cette façon, vous testez indirectement ce que font les méthodes privées et maintenez un niveau d'agilité plus élevé.


76
+1 à cela. À mon avis, c'est la meilleure réponse à la question. En testant des méthodes privées, vous testez l'implémentation. Cela va à l'encontre de l'objectif des tests unitaires, qui est de tester les entrées / sorties d'un contrat de classe. Un test doit seulement savoir assez sur la mise en œuvre se moquer des méthodes qu'elle appelle ses dépendances. Rien de plus. Si vous ne pouvez pas modifier votre implémentation sans avoir à changer de test, il est probable que votre stratégie de test soit mauvaise.
Colin M

10
@Colin M Mais ce n'est pas vraiment ce qu'il demande;) Laissez-le décider, vous ne connaissez pas le projet.
Aaron Marcus

2
Pas vraiment vrai. Il pourrait prendre beaucoup d'efforts pour tester une petite partie de la méthode privée à travers la méthode publique l'utilisant. La méthode publique peut nécessiter une configuration importante avant d'atteindre la ligne qui appelle votre méthode privée
ACV

1
Je suis d'accord. Vous devez tester si le contrat est rempli. Vous ne devriez pas tester comment cela se fait. Si le contact est rempli sans atteindre la couverture de code à 100% (dans les méthodes privées), cela peut être un code mort ou inutile.
wuppi

207

De cet article: Tester des méthodes privées avec JUnit et SuiteRunner (Bill Venners), vous avez essentiellement 4 options:

  1. Ne testez pas les méthodes privées.
  2. Accordez l'accès au package de méthodes.
  3. Utilisez une classe de test imbriquée.
  4. Utilisez la réflexion.

@JimmyT., Dépend de qui est destiné le "code de production". J'appellerais le code produit pour les applications stationnées dans VPN dont les utilisateurs cibles sont des administrateurs système pour être du code de production.
Pacerier

@Pacerier Que voulez-vous dire?
Jimmy T.

1
La 5ème option, comme mentionné ci-dessus, teste la méthode publique qui appelle la méthode privée? Correct?
user2441441

1
@LorenzoLerate J'ai lutté avec ça parce que je fais du développement embarqué et j'aimerais que mon logiciel soit aussi petit que possible. Les contraintes de taille et les performances sont la seule raison à laquelle je peux penser.

J'aime vraiment le test en utilisant la réflexion: ici le modèle Method Method = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid

127

Généralement, un test unitaire est destiné à exercer l'interface publique d'une classe ou d'une unité. Par conséquent, les méthodes privées sont des détails d'implémentation que vous ne vous attendez pas à tester explicitement.


16
C'est la meilleure réponse OMI, ou comme on le dit habituellement, testez le comportement, pas les méthodes. Les tests unitaires ne remplacent pas les mesures de code source, les outils d'analyse de code statique et les révisions de code. Si les méthodes privées sont si complexes qu'elles nécessitent des tests séparés, elles doivent probablement être refactorisées, et non plus de tests.
Dan Haynes

14
alors n'écrivez pas de méthodes privées, créez simplement 10 autres petites classes?
rasoir

1
Non, cela signifie essentiellement que vous pouvez tester la fonctionnalité des méthodes privées à l'aide de la méthode publique qui les appelle.
Akshat Sharda

67

Juste deux exemples où je voudrais tester une méthode privée:

  1. Routines de déchiffrement - Je ne voudrais pas les rendre visibles à quiconque pour voir juste pour le test, sinon n'importe qui peut les utiliser pour déchiffrer. Mais ils sont intrinsèques au code, compliqués et doivent toujours fonctionner (l'exception évidente est la réflexion qui peut être utilisée pour afficher même les méthodes privées dans la plupart des cas, lorsqu'elle SecurityManagern'est pas configurée pour empêcher cela).
  2. Création d'un SDK pour la consommation communautaire. Ici, le public prend un sens complètement différent, car il s'agit d'un code que le monde entier peut voir (pas seulement à l'intérieur de mon application). Je mets du code dans des méthodes privées si je ne veux pas que les utilisateurs du SDK le voient - je ne vois pas cela comme une odeur de code, mais simplement comme le fonctionnement de la programmation du SDK. Mais bien sûr, je dois encore tester mes méthodes privées, et c'est là que les fonctionnalités de mon SDK se trouvent réellement.

Je comprends l'idée de ne tester que le "contrat". Mais je ne vois pas que l'on puisse préconiser de ne pas tester le code - votre kilométrage peut varier.

Mon compromis consiste donc à compliquer les JUnits avec réflexion, plutôt qu'à compromettre ma sécurité et mon SDK.


5
Bien que vous ne deviez jamais rendre une méthode publique juste pour la tester, ce privaten'est pas la seule alternative. Aucun modificateur d'accès n'est package privateet signifie que vous pouvez le tester à l'unité tant que votre test unitaire vit dans le même package.
ngreen

1
Je commentais votre réponse, point 1 en particulier, pas le PO. Il n'est pas nécessaire de rendre une méthode privée simplement parce que vous ne voulez pas qu'elle soit publique.
ngreen

1
@ngreen true thx - J'étais paresseux avec le mot "public". J'ai mis à jour la réponse pour inclure public, protected, default (et pour mentionner la réflexion). Le point que j'essayais de faire est qu'il y a une bonne raison pour qu'un code soit secret, mais cela ne devrait pas nous empêcher de le tester.
Richard Le Mesurier

2
Merci d'avoir donné de vrais exemples à tout le monde. La plupart des réponses sont bonnes, mais d'un point de vue théorique. Dans le monde réel, nous devons cacher l'implémentation du contrat, et nous devons encore le tester. En lisant tout cela, je pense que cela devrait être un tout nouveau niveau de test, avec un nom différent
Z. Khullah

1
Un autre exemple - analyseurs HTML Crawler . Devoir analyser une page html entière en objets de domaine nécessite une tonne de logique de validation de petites parties de la structure. Il est logique de décomposer cela en méthodes et de l'appeler à partir d'une méthode publique, mais cela n'a pas de sens que toutes les méthodes plus petites qui le composent soient publiques, ni de créer 10 classes par page HTML.
Nether

59

Les méthodes privées sont appelées par une méthode publique, donc les entrées de vos méthodes publiques doivent également tester les méthodes privées qui sont appelées par ces méthodes publiques. Lorsqu'une méthode publique échoue, cela peut être un échec dans la méthode privée.


Fait réel. Le problème est lorsque l'implémentation est plus compliquée, comme si une méthode publique appelle plusieurs méthodes privées. Lequel a échoué? Ou s'il s'agit d'une méthode privée compliquée, qui ne peut pas être exposée ni transférée dans une nouvelle classe
Z. Khullah

Vous avez déjà mentionné pourquoi la méthode privée devrait être testée. Vous avez dit "alors ça pourrait être", donc vous n'êtes pas sûr. OMI, si une méthode doit être testée est orthogonale à son niveau d'accès.
Wei Qiu

39

Une autre approche que j'ai utilisée consiste à changer une méthode privée pour empaqueter privé ou protégé, puis à la compléter avec l' annotation @VisibleForTesting de la bibliothèque Google Guava.

Cela indiquera à quiconque utilisant cette méthode de prendre des précautions et de ne pas y accéder directement, même dans un package. De même, une classe de test n'a pas besoin d'être physiquement dans le même package , mais dans le même package sous le dossier de test .

Par exemple, si une méthode à tester est présente, src/main/java/mypackage/MyClass.javavotre appel de test doit être placé src/test/java/mypackage/MyClassTest.java. De cette façon, vous avez accès à la méthode de test dans votre classe de test.


1
Je ne connaissais pas celui-ci, c'est intéressant, je pense toujours que si vous avez besoin de ce genre d'annotation, vous avez des problèmes de conception.
Seb

2
Pour moi, c'est comme dire au lieu de donner une clé unique à votre pompier pour tester l'exercice d'incendie, vous laissez votre porte d'entrée déverrouillée avec un signe sur la porte disant - "déverrouillé pour les tests - si vous n'êtes pas de notre entreprise, veuillez ne pas entrer ".
Père Jeremy Krieg,

@FrJeremyKrieg - qu'est-ce que cela signifie?
MasterJoe2

1
@ MasterJoe2: le but d'un test incendie est d'améliorer la sécurité de votre bâtiment contre les risques d'incendie. Mais vous devez donner au directeur l'accès à votre immeuble. Si vous laissez la porte d'entrée déverrouillée en permanence, vous augmentez le risque d'accès non autorisé et la rendez moins sûre. Il en va de même pour le modèle VisibleForTesting - vous augmentez le risque d'accès non autorisé afin de réduire le risque de «feu». Mieux vaut accorder un accès unique pour les tests uniquement lorsque vous en avez besoin (par exemple, en utilisant la réflexion) plutôt que de le laisser déverrouillé en permanence (non privé).
Père Jeremy Krieg

34

Pour tester du code hérité avec des classes volumineuses et originales, il est souvent très utile de pouvoir tester la seule méthode privée (ou publique) que j'écris en ce moment .

J'utilise le package junitx.util.PrivateAccessor pour Java. Beaucoup de lignes utiles pour accéder aux méthodes privées et aux champs privés.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

2
Assurez-vous de télécharger l'intégralité du package JUnit-addons ( sourceforge.net/projects/junit-addons ), pas le PrivateAccessor Project Forge-Recommended Project.
skia.heliou

2
Et assurez-vous que lorsque vous utilisez a Classcomme premier paramètre dans ces méthodes, vous n'accédez qu'aux staticmembres.
skia.heliou

31

Dans Spring Framework, vous pouvez tester des méthodes privées à l'aide de cette méthode:

ReflectionTestUtils.invokeMethod()

Par exemple:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

La solution la plus propre et la plus concise à ce jour, mais uniquement si vous utilisez Spring.
Denis Nikolaenko

28

Après avoir essayé la solution de Cem Catikkas en utilisant la réflexion pour Java, je dois dire que c'était une solution plus élégante que celle que j'ai décrite ici. Cependant, si vous cherchez une alternative à l'utilisation de la réflexion et avez accès à la source que vous testez, ce sera toujours une option.

Il est possible de tester les méthodes privées d'une classe, en particulier avec le développement piloté par les tests , où vous souhaitez concevoir de petits tests avant d'écrire du code.

La création d'un test avec accès aux membres privés et aux méthodes peut tester des zones de code difficiles à cibler spécifiquement avec un accès uniquement aux méthodes publiques. Si une méthode publique comporte plusieurs étapes, elle peut consister en plusieurs méthodes privées, qui peuvent ensuite être testées individuellement.

Avantages:

  • Peut tester une granularité plus fine

Désavantages:

  • Le code de test doit résider dans le même fichier que le code source, ce qui peut être plus difficile à maintenir
  • De même avec les fichiers de sortie .class, ils doivent rester dans le même package que celui déclaré dans le code source

Cependant, si des tests continus nécessitent cette méthode, cela peut être un signal que les méthodes privées doivent être extraites, qui pourraient être testées de manière traditionnelle et publique.

Voici un exemple compliqué de la façon dont cela fonctionnerait:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

La classe interne serait compilée pour ClassToTest$StaticInnerTest.

Voir aussi: Astuce Java 106: classes internes statiques pour le plaisir et le profit


26

Comme d'autres l'ont dit ... ne testez pas directement les méthodes privées. Voici quelques réflexions:

  1. Gardez toutes les méthodes petites et ciblées (faciles à tester, faciles à trouver ce qui ne va pas)
  2. Utilisez des outils de couverture de code. J'aime Cobertura (oh happy day, on dirait qu'une nouvelle version est sortie!)

Exécutez la couverture du code sur les tests unitaires. Si vous voyez que les méthodes ne sont pas entièrement testées, ajoutez aux tests pour augmenter la couverture. Visez une couverture de code à 100%, mais réalisez que vous ne l'obtiendrez probablement pas.


Prêt pour la couverture du code. Quel que soit le type de logique dans la méthode privée, vous invoquez toujours cette logique via une méthode publique. Un outil de couverture de code peut vous montrer quelles parties sont couvertes par le test, vous pouvez donc voir si votre méthode privée est testée.
coolcfan

J'ai vu des cours où la seule méthode publique est principale [], et ils font apparaître une interface graphique et connectent la base de données et quelques serveurs Web dans le monde entier. Facile à dire "ne testez pas indirectement" ...
Audrius Meskauskas

26

Les méthodes privées sont consommées par les méthodes publiques. Sinon, ce sont des codes morts. C'est pourquoi vous testez la méthode publique, affirmant les résultats attendus de la méthode publique et donc les méthodes privées qu'elle consomme.

Le test des méthodes privées doit être testé par débogage avant d'exécuter vos tests unitaires sur les méthodes publiques.

Ils peuvent également être débogués à l'aide d'un développement piloté par les tests, déboguant vos tests unitaires jusqu'à ce que toutes vos assertions soient satisfaites.

Je pense personnellement qu'il vaut mieux créer des classes en utilisant TDD; créer les talons de méthode publique, puis générer des tests unitaires avec toutes les assertions définies à l'avance, de sorte que le résultat attendu de la méthode soit déterminé avant de la coder. De cette façon, vous n'allez pas dans la mauvaise direction pour que les assertions des tests unitaires correspondent aux résultats. Votre classe est alors robuste et répond aux exigences lorsque tous vos tests unitaires réussissent.


2
Bien que cela soit vrai, cela peut conduire à des tests très compliqués - c'est un meilleur modèle pour tester une seule méthode à la fois (tests unitaires) plutôt qu'un groupe entier d'entre eux. Pas une suggestion terrible, mais je pense qu'il y a de meilleures réponses ici - cela devrait être un dernier recours.
Bill K

24

Si vous utilisez Spring, ReflectionTestUtils fournit des outils pratiques qui vous aident ici avec un effort minimal. Par exemple, pour configurer une maquette sur un membre privé sans être obligé d'ajouter un setter public indésirable:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

Si vous essayez de tester du code existant que vous êtes réticent ou incapable de changer, la réflexion est un bon choix.

Si la conception de la classe est toujours flexible et que vous avez une méthode privée compliquée que vous souhaitez tester séparément, je vous suggère de la retirer dans une classe distincte et de tester cette classe séparément. Cela ne doit pas changer l'interface publique de la classe d'origine; il peut créer en interne une instance de la classe d'assistance et appeler la méthode d'assistance.

Si vous souhaitez tester des conditions d'erreur difficiles provenant de la méthode d'assistance, vous pouvez aller plus loin. Extraire une interface de la classe d'assistance, ajouter un getter et un setter publics à la classe d'origine pour injecter la classe d'assistance (utilisée via son interface), puis injecter une version fictive de la classe d'assistance dans la classe d'origine pour tester le fonctionnement de la classe d'origine répond aux exceptions de l'aide. Cette approche est également utile si vous souhaitez tester la classe d'origine sans également tester la classe d'assistance.


19

Le test des méthodes privées rompt l'encapsulation de votre classe car chaque fois que vous modifiez l'implémentation interne, vous cassez le code client (dans ce cas, les tests).

Alors ne testez pas les méthodes privées.


4
le test unitaire et le code src sont une paire. Si vous changez le src, vous devrez peut-être changer le test unitaire. C'est le sens du test junit. Ils garantiront que tout fonctionne comme avant. et c'est bien s'ils se cassent, si vous changez le code.
AlexWien

1
Ne convenez pas que le test unitaire devrait changer si le code change. Si vous êtes invité à ajouter des fonctionnalités à une classe sans modifier les fonctionnalités existantes de la classe, les tests unitaires existants doivent réussir même après avoir modifié votre code. Comme Peter le mentionne, les tests unitaires devraient tester l'interface, pas comment elle est effectuée en interne. Dans Test Driven Development, les tests unitaires sont créés avant l'écriture du code, pour se concentrer sur l'interface de la classe, pas sur la façon dont il est résolu en interne.
Harald Coppoolse

16

La réponse de la page FAQ de JUnit.org :

Mais si vous devez ...

Si vous utilisez JDK 1.3 ou supérieur, vous pouvez utiliser la réflexion pour renverser le mécanisme de contrôle d'accès à l'aide de PrivilegedAccessor . Pour plus de détails sur son utilisation, lisez cet article .

Si vous utilisez JDK 1.6 ou supérieur et que vous annotez vos tests avec @Test, vous pouvez utiliser Dp4j pour injecter de la réflexion dans vos méthodes de test. Pour plus de détails sur son utilisation, consultez ce script de test .

PS Je suis le principal contributeur de Dp4j , demandez- moi si vous avez besoin d'aide. :)


16

Si vous souhaitez tester des méthodes privées d'une application héritée où vous ne pouvez pas modifier le code, une option pour Java est jMockit , qui vous permettra de créer des simulations sur un objet même lorsqu'elles sont privées pour la classe.


4
Dans le cadre de la bibliothèque jmockit, vous avez accès à la classe de désencapsulation qui facilite le test des méthodes privées: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

J'utilise cette approche tout le temps. Très utile
MedicineMan

14

J'ai tendance à ne pas tester de méthodes privées. Là réside la folie. Personnellement, je pense que vous ne devriez tester que vos interfaces exposées publiquement (et cela inclut les méthodes protégées et internes).


14

Si vous utilisez JUnit, jetez un œil aux add-ons junit . Il a la capacité d'ignorer le modèle de sécurité Java et d'accéder aux méthodes et attributs privés.


13

Je vous suggère de refactoriser un peu votre code. Lorsque vous devez commencer à penser à utiliser la réflexion ou d'autres types de choses, pour simplement tester votre code, quelque chose ne va pas avec votre code.

Vous avez mentionné différents types de problèmes. Commençons par les champs privés. Dans le cas de champs privés, j'aurais ajouté un nouveau constructeur et injecté des champs dans celui-ci. Au lieu de cela:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

J'aurais utilisé ceci:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Ce ne sera pas un problème, même avec du code hérité. L'ancien code utilisera un constructeur vide, et si vous me le demandez, le code refactorisé sera plus propre et vous pourrez injecter les valeurs nécessaires dans le test sans réflexion.

Maintenant sur les méthodes privées. D'après mon expérience personnelle, lorsque vous devez écraser une méthode privée pour les tests, cette méthode n'a rien à voir dans cette classe. Un modèle commun, dans ce cas, serait de l' envelopper dans une interface, comme Callableet vous passez ensuite cette interface également dans le constructeur (avec cette astuce de constructeur multiple):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

La plupart du temps, tout ce que j'ai écrit ressemble à un schéma d'injection de dépendance. D'après mon expérience personnelle, c'est vraiment utile lors des tests, et je pense que ce type de code est plus propre et sera plus facile à maintenir. Je dirais la même chose des classes imbriquées. Si une classe imbriquée contient une logique lourde, il serait préférable de la déplacer en tant que classe privée de package et de l'injecter dans une classe qui en a besoin.

Il existe également plusieurs autres modèles de conception que j'ai utilisés lors de la refactorisation et de la maintenance du code hérité, mais tout dépend des cas de votre code à tester. L'utilisation de la réflexion n'est généralement pas un problème, mais lorsque vous avez une application d'entreprise qui est fortement testée et que les tests sont exécutés avant chaque déploiement, tout devient vraiment lent (c'est juste ennuyeux et je n'aime pas ce genre de choses).

Il y a aussi l'injection de setter, mais je ne recommanderais pas de l'utiliser. Je préfère m'en tenir à un constructeur et initialiser tout quand c'est vraiment nécessaire, en laissant la possibilité d'injecter les dépendances nécessaires.


1
Pas d'accord avec l' ClassToTest(Callable)injection. Cela rend ClassToTestplus compliqué. Rester simple. En outre, cela nécessite alors que quelqu'un d'autre dise ClassToTestquelque chose qui ClassToTestdevrait être le patron de. Il y a une place pour injecter de la logique, mais ce n'est pas ça. Vous venez de rendre la classe plus difficile à maintenir, pas plus facile.
Loduwijk

De plus, il est préférable que votre méthode de test Xn'augmente pas la Xcomplexité au point où cela pourrait causer plus de problèmes ... et donc nécessiter des tests supplémentaires ... ce qui si vous avez implémenté d'une manière qui peut causer plus de problèmes .. (ce n'est pas une boucle infinie; chaque itération est probablement plus petite que la précédente, mais toujours ennuyeuse)
Loduwijk

2
Pourriez-vous expliquer pourquoi cela rendrait la classe ClassToTestplus difficile à maintenir? En fait, cela facilite la maintenance de votre application, que suggérez-vous de créer de nouvelles classes pour chaque valeur différente dont vous aurez besoin firstet de «secondes» variables?
GROX13

1
La méthode de test n'augmente pas la complexité. C'est juste votre classe qui est mal écrite, si mal qu'elle ne peut pas être testée.
GROX13

Plus difficile à maintenir car la classe est plus compliquée. class A{ A(){} f(){} }est plus simple que class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Si ce n'était pas vrai, et s'il était vrai que fournir la logique d'une classe comme arguments de constructeur était plus facile à maintenir, alors nous passerions toute la logique comme arguments de constructeur. Je ne vois tout simplement pas comment vous pouvez prétendre class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }rendre A plus facile à entretenir. Il y a des cas où l'injection comme ça a du sens, mais je ne vois pas votre exemple comme "plus facile à maintenir"
Loduwijk

12

Une méthode privée n'est accessible que dans la même classe. Il n'y a donc aucun moyen de tester une méthode «privée» d'une classe cible à partir d'une classe de test. Une solution consiste à effectuer manuellement des tests unitaires ou à changer votre méthode de «privé» à «protégé».

Et puis une méthode protégée n'est accessible que dans le même package où la classe est définie. Donc, tester une méthode protégée d'une classe cible signifie que nous devons définir votre classe de test dans le même package que la classe cible.

Si tout ce qui précède ne correspond pas à vos besoins, utilisez la méthode de réflexion pour accéder à la méthode privée.


Vous mélangez «protégé» avec «amical». Une méthode protégée n'est accessible qu'à une classe dont les objets sont assignables à la classe cible (c'est-à-dire les sous-classes).
Sayo Oladeji

1
En fait, il n'y a pas de "Friendly" en Java, le terme est "Package" et il est indiqué par l'absence d'un modificateur privé / public / protégé. J'aurais juste corrigé cette réponse, mais il y en a une très bonne qui dit déjà cela - je recommanderais donc simplement de la supprimer.
Bill K

J'ai presque rétrogradé, pensant tout le temps "Cette réponse est tout simplement fausse." jusqu'à ce que j'arrive à la toute dernière phrase, ce qui contredit le reste de la réponse. La dernière phrase aurait dû être la première.
Loduwijk

11

Comme beaucoup l'ont suggéré ci-dessus, un bon moyen est de les tester via vos interfaces publiques.

Si vous faites cela, c'est une bonne idée d'utiliser un outil de couverture de code (comme Emma) pour voir si vos méthodes privées sont en fait exécutées à partir de vos tests.


Vous ne devriez pas tester indirectement! Non seulement toucher via la couverture; tester que le résultat attendu est livré!
AlexWien

11

Voici ma fonction générique pour tester des champs privés:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

10

Veuillez voir ci-dessous pour un exemple;

La déclaration d'importation suivante doit être ajoutée:

import org.powermock.reflect.Whitebox;

Vous pouvez maintenant passer directement l'objet qui a la méthode privée, le nom de la méthode à appeler et des paramètres supplémentaires comme ci-dessous.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

Aujourd'hui, j'ai poussé une bibliothèque Java pour aider à tester des méthodes et des champs privés. Il a été conçu avec Android à l'esprit, mais il peut vraiment être utilisé pour n'importe quel projet Java.

Si vous avez obtenu du code avec des méthodes ou des champs ou des constructeurs privés, vous pouvez utiliser BoundBox . Il fait exactement ce que vous recherchez. Voici ci-dessous un exemple de test qui accède à deux champs privés d'une activité Android pour la tester:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

BoundBox facilite le test de champs, méthodes et constructeurs privés / protégés. Vous pouvez même accéder à des éléments cachés par héritage. En effet, BoundBox rompt l'encapsulation. Il vous donnera accès à tout cela grâce à la réflexion, MAIS tout est vérifié au moment de la compilation.

Il est idéal pour tester du code hérité. Utilisez-le soigneusement. ;)

https://github.com/stephanenicolas/boundbox


1
Je viens de l'essayer, BoundBox est une solution simple et élégante!
forhas

9

Tout d'abord, je vais jeter cette question: pourquoi vos membres privés ont-ils besoin de tests isolés? Sont-ils si complexes, offrant des comportements compliqués au point de nécessiter des tests en dehors de la surface publique? Ce sont des tests unitaires, pas des tests de «ligne de code». Ne transpirez pas les petites choses.

S'ils sont si grands, assez grands pour que ces membres privés soient chacun une «unité» de grande complexité - pensez à refactoriser ces membres privés de cette classe.

Si la refactorisation est inappropriée ou irréalisable, pouvez-vous utiliser le modèle de stratégie pour remplacer l'accès à ces fonctions / classes membres privées lors d'un test unitaire? Dans le cadre d'un test unitaire, la stratégie fournirait une validation supplémentaire, mais dans les versions de version, ce serait un simple passthrough.


3
Parce que souvent un morceau particulier de code d'une méthode publique est refactorisé en une méthode privée interne et est vraiment la pièce critique de la logique que vous pourriez avoir mal. Vous voulez tester cela indépendamment de la méthode publique
oxbow_lakes

Même le code le plus court parfois sans test unitaire n'est pas correct. Essayez simplement de calculer la différence entre 2 angles géographiques. 4 lignes de code, et la plupart ne le feront pas correctement au premier essai. De telles méthodes nécessitent un test unitaire, car elles forment la base d'un code de confiance. (Un tel code utile peut aussi être public; moins protégé utile
AlexWien

8

J'ai récemment eu ce problème et j'ai écrit un petit outil, appelé Picklock , qui évite les problèmes d'utilisation explicite de l'API de réflexion Java, deux exemples:

Méthodes d'appel, par exemple private void method(String s)- par réflexion Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Méthodes d'appel, par exemple private void method(String s)- par Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Définition de champs, par exemple private BigInteger amount;- par réflexion Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Définition de champs, par exemple private BigInteger amount;- par Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

7

PowerMockito est fait pour cela. Utiliser la dépendance maven

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Ensuite, vous pouvez faire

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.