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

Comment tester à l'unité (en utilisant xUnit) une classe qui a des méthodes privées internes, des champs ou des classes imbriquées? Ou une fonction qui est rendue privée en ayant linkage interne ( static en C/C++) ou est dans un espace de nom privé ( anonymous )?

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

2324
demandé sur Raedwald 2008-08-29 20:11:09

30 réponses

si vous avez un peu d'un héritage Java application, et vous n'êtes pas autorisé à changer la visibilité de vos méthodes, la meilleure façon de tester les méthodes privées est d'utiliser réflexion .

à l'interne, nous utilisons des helpers pour obtenir/définir les variables private et private static ainsi que pour invoquer les méthodes private et private static . Les modèles suivants vous permettront de faire à peu près tout ce qui concerne méthodes et domaines privés. Bien sûr, vous ne pouvez pas changer les variables private static final par la 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);

Notes:

1. targetClass.getDeclaredMethod(methodName, argClasses) permet d'examiner les méthodes private . La même chose s'applique pour getDeclaredField .

2. Le setAccessible(true) est nécessaire pour jouer avec les soldats.

1443
répondu Cem Catikkas 2017-12-14 17:14:57

la meilleure façon de tester une méthode privée est d'utiliser une autre méthode publique. Si cela ne peut pas être fait, alors l'une des conditions suivantes est vraie:

  1. La méthode privée est morte code
  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""
519
répondu Trumpi 2008-08-29 16:14:19

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

mon approche habituelle pour aborder ces questions est de Teaser une nouvelle classe qui contient les bits intéressants. Souvent, cette méthode et les champs, il interagit avec, et peut-être une autre méthode ou deux peuvent être extraites dans une nouvelle classe.

la nouvelle classe expose ces méthodes en tant que 'public', de sorte qu'elles sont accessibles pour les tests à l'unité. Les nouvelles et anciennes classes sont maintenant à la fois plus simples que la classe d'origine, ce qui est génial pour moi (je dois garder les choses simples, ou je me perds!).

notez que je ne suggère pas que les gens créent des classes sans utiliser leur cerveau! Le but ici est d'utiliser les forces du test d'unité pour vous aider à trouver de bonnes nouvelles classes.

275
répondu Jay Bazuzi 2016-05-19 09:38:59

j'ai utilisé réflexion pour ce faire de Java dans le passé, et à mon avis c'était une grosse erreur.

Strictement parlant, il faut pas l'écriture de tests unitaires que de tester directement les méthodes privées. Ce que vous devrait Tester est le contrat public que la classe A avec d'autres objets; vous ne devriez jamais tester directement les internes d'un objet. Si un autre développeur veut faire un petit changement 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 faites cela à 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 nuisance pour l'équipe de développement.

ce que je recommande plutôt est d'utiliser un outil de couverture de code tel que Cobertura, pour s'assurer que l'unité teste vous écrivez fournir une couverture décente du code dans les méthodes privées. De cette façon, vous testez indirectement ce que les méthodes privées font, et maintenir un niveau plus élevé d'agilité.

236
répondu Jon 2017-12-14 12:19:35

de cet article: Testing Private Methods with JUnit and SuiteRunner (Bill Venners), vous avez essentiellement 4 options:

  • ne testez pas les méthodes privées.
  • donne accès au Paquet Méthodes.
  • utiliser une classe d'essai imbriquée.
  • utiliser la réflexion.
187
répondu Iker Jimenez 2012-09-21 20:45:56

en général, un critère unitaire est conçu pour exercer l'interface publique d'une classe ou d'une unité. Par conséquent, les méthodes privées sont des détails de mise en œuvre que vous ne vous attendez pas à tester explicitement.

99
répondu John Channing 2012-12-29 09:52:20

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

  1. routines de déchiffrement - Je ne voudrais pas souhaitez rendre visibles à tous juste pour le souci de test, sinon n'importe qui peut les utiliser pour décrypter. Mais ils sont intrinsèque du code compliqué, et la nécessité de toujours travailler (l'exception évidente est la réflexion qui peut être utilisée pour visualiser même les méthodes privées dans la plupart des cas, quand SecurityManager n'est pas configuré pour éviter cela).
  2. création d'un SDK pour la communauté consommation. Ici, le public prend une complètement différent du sens, puisque cet est un code que le monde entier peut voir (et pas seulement interne à ma demande). J'ai mis code dans des méthodes privées si Je ne le fais pas vous voulez que les utilisateurs du SDK le voient? ne voyez pas cela comme odeur de code, simplement comment fonctionne la programmation SDK. Mais de bien sûr, je dois encore tester mon les méthodes privées, et ils sont où la fonctionnalité de mon SDK réellement vit.

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

donc mon compromis implique de compliquer les JUnits avec réflexion, plutôt que de compromettre ma sécurité & SDK.

56
répondu Richard Le Mesurier 2017-01-13 19:24:04

les méthodes privées sont appelées par une méthode publique, donc les entrées à vos méthodes publiques devraient également tester les méthodes privées qui sont appelées par ces méthodes publiques. Quand une méthode publique échoue, alors ce pourrait être un échec dans la méthode privée.

53
répondu Thomas Owens 2008-08-29 16:15:30

une autre approche que j'ai utilisée est de changer une méthode privée pour empaqueter privé ou protégé puis le compléter avec le @VisibleForTesting annotation de la Bibliothèque Google Guava.

cela indiquera à toute personne utilisant cette méthode de prendre des précautions et de ne pas y accéder directement, même dans un paquet. En outre, une classe d'épreuve ne doit pas nécessairement se trouver dans le même colis physiquement , mais dans le même colis sous le test dossier.

par exemple, si une méthode à tester est dans src/main/java/mypackage/MyClass.java alors votre appel de test devrait être placé dans src/test/java/mypackage/MyClassTest.java . De cette façon, vous avez accès à la méthode d'essai dans votre classe de test.

30
répondu Peter Mortensen 2017-01-13 19:58:57

pour tester le code d'héritage avec des classes larges et excentriques, il est souvent très utile de pouvoir tester la méthode une privée (ou publique) que j'écris en ce moment .

j'utilise le junitx.util.PrivateAccessor - package for Java . Beaucoup de liners utiles pour accéder à des méthodes privées et des champs privés.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
28
répondu phareim 2017-12-14 12:20:38

après avoir essayé la solution de Cem Catikkas utilisant la réflexion pour Java, je dois dire que sa solution était plus élégante que je l'ai décrit ici. Cependant, si vous êtes à la recherche d'une alternative à l'utilisation de la réflexion, et que vous 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 développement basé sur les tests , où vous pourriez comme pour concevoir de petits tests avant d'écrire n'importe quel code.

la création d'un test avec accès aux membres privés et aux méthodes peut tester des zones de code qui sont difficiles à cibler spécifiquement avec accès seulement 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

inconvénients:

  • le code D'essai doit résider dans le même fichier de code source, qui peut être plus difficile à maintenir
  • de même avec .class output files, ils doivent rester dans le même paquet que déclaré dans le code source

toutefois, si l'essai en continu nécessite cette méthode, il peut s'agir d'un signal indiquant que les méthodes privées doivent être extraites, ce qui pourrait être testé dans la traditionnel, sur la voie publique.

voici un exemple alambiqué 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 intérieure serait compilée en ClassToTest$StaticInnerTest .

Voir aussi: Java Astuce 106: Statique à l'intérieur des classes pour le plaisir et le profit

24
répondu Grundlefleck 2017-12-14 12:22:32

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

  1. garder toutes les méthodes petites et ciblées (facile à tester, facile à trouver ce qui ne va pas)
  2. utiliser des outils de couverture de code. J'aime Couverture (oh jour heureux, ressemble à une nouvelle version est sortie!)

exécuter la couverture du code sur les essais unitaires. Si vous voyez que les méthodes ne sont pas entièrement testé ajouter à la des tests pour augmenter la couverture. Visez une couverture 100% code, mais réalisez que vous ne l'obtiendrez probablement pas.

21
répondu TofuBeer 2017-01-13 19:13:20

les méthodes privées sont consommées par les méthodes publiques. Sinon, ils sont morts de code. 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.

tester les méthodes privées doit être testé par débogage avant d'exécuter les tests de votre unité sur les méthodes publiques.

ils peuvent également être débogués en utilisant le développement piloté par les tests, déboguage de vos tests unitaires jusqu'à ce que toutes vos assertions sont rencontrer.

je crois personnellement qu'il est préférable de créer des classes en utilisant TDD; créer les talons de méthode publique, puis générer des tests unitaires avec tous les assertions définies à l'avance, de sorte que le résultat attendu de la méthode est déterminé avant de le coder. De cette façon, vous ne vous engagez pas dans la mauvaise voie en faisant correspondre les assertions des tests unitaires aux résultats. Votre classe est alors robuste et répond aux exigences lorsque tous les tests de votre unité sont réussis.

20
répondu Antony Booth 2017-01-13 19:33:13

si vous utilisez le ressort, ReflectionTestUtils fournit quelques outils pratiques qui aident ici avec un effort minimal. Par exemple, pour mettre en place un mock sur un député sans être forcé d'ajouter un setter public indésirable:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
19
répondu Steve Chambers 2014-11-10 23:36:00

si vous essayez de tester le 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 encore flexible, et que vous avez une méthode privée compliquée que vous aimeriez tester séparément, je vous suggère de le retirer dans une classe séparée et tester cette classe séparément. Ceci n'a pas à changer l'interface publique de la classe d'origine; il peut créer en interne une instance de la classe helper et appeler le méthode d'aide.

si vous voulez tester des conditions d'erreur difficiles provenant de la méthode helper, vous pouvez aller un peu plus loin. Extraire une interface de la classe helper, ajouter un getter et un setter publics à la classe originale pour injecter la classe helper (utilisée par son interface), puis injecter une version simulée de la classe helper dans la classe originale pour tester comment la classe originale répond aux exceptions de l'helper. Cette approche est également utile si vous voulez tester la classe originale sans aussi tester la classe helper.

19
répondu Don Kirkby 2017-01-13 19:08:28

Dans le Spring vous pouvez tester les méthodes privées à l'aide de cette méthode:

ReflectionTestUtils.invokeMethod()

par exemple:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
17
répondu Mikhail 2017-01-13 19:57:12

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

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

15
répondu Peter Mortensen 2012-12-29 10:02:43

si vous voulez tester des méthodes privées d'une application héritée où vous ne pouvez pas changer le code, une option pour Java est jMockit , qui vous permettra de créer des moqueries à un objet même s'il est privé de la classe.

14
répondu Will Sargent 2017-12-14 12:23:33

si vous utilisez JUnit, consultez junit-addons . 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
répondu Joe 2017-12-14 12:24:27

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

12
répondu bmurphy1976 2008-08-29 16:23:45

la réponse de JUnit.org FAQ page :

mais si vous le devez...

si vous utilisez JDK 1.3 ou plus, vous pouvez utiliser la réflexion pour subvertir le mécanisme de contrôle d'accès à l'aide du PrivilegedAccessor . Pour plus de détails sur la façon de l'utiliser, lire 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 la réflexion dans vos méthodes d'essai. Pour détails sur la façon de l'utiliser, voir ce script de test .

P.S. Je suis le principal contributeur à Dp4j , demandez me si vous avez besoin d'aide. :)

12
répondu simpatico 2017-05-23 11:55:10

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 dans n'importe quelle classe test. Une solution est que vous pouvez effectuer des tests unitaires manuellement ou peut changer votre méthode de "privé" à "protégé".

Et ensuite une méthode protégée ne peut être consulté dans le même paquet, 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 paquet que la classe cible.

si tout ce qui précède ne correspond pas à vos besoins, utilisez the reflection way pour accéder à la méthode privée.

11
répondu loknath 2017-05-23 12:18:29

je vous suggère de remanier un peu votre code. Quand vous devez commencer à penser à utiliser la réflexion ou d'autres genres de choses, juste pour 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é dans les champs. 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 d'héritage. L'ancien code utilisera un constructeur vide, et si vous me demandez, le code refactorisé aura l'air plus propre, et vous pourrez injecter les valeurs nécessaires dans test sans réflexion.

maintenant sur les méthodes privées. D'après mon expérience personnelle, quand vous devez couper une méthode privée pour le test, alors cette méthode n'a rien à faire dans cette classe. Un modèle commun, dans ce cas, serait de wrap il dans une interface, comme Callable et puis vous passer dans cette interface aussi dans le constructeur (avec ce tour de constructeur multiple):

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

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

Tout ce que j'ai écrit ressemble à un schéma d'injection de dépendance. Dans mon expérience personnelle, il est vraiment utile pour tester, et je pense que ce genre de code est plus propre et sera plus facile à maintenir. Je dirais la même chose pour les classes imbriquées. Si une classe imbriquée contient lourd logique il vaudrait mieux que vous l'ayez déplacée en classe privée et que vous l'ayez injectée dans une classe qui en a besoin.

Il ya aussi plusieurs autres modèles de conception que j'ai utilisés tout en remodelant et en maintenant le code d'héritage, mais tout dépend des cas de votre code à tester. Utiliser la réflexion la plupart du temps n'est pas un problème, mais quand vous avez une application d'entreprise qui est lourdement testé et des tests sont effectués avant chaque déploiement tout devient vraiment lent (c'est juste ennuyeux et je n'aime pas ce genre de trucs).

il y a aussi setter injection, mais je ne recommande pas de l'utiliser. Je ferais mieux de rester avec un constructeur et d'initialiser tout quand c'est vraiment nécessaire, en laissant la possibilité d'injecter les dépendances nécessaires.

11
répondu GROX13 2017-01-13 20:05:12

comme beaucoup ci-dessus ont suggéré, une bonne façon 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.

10
répondu Darren Greaves 2008-09-10 07:48:42

voici ma fonction générique pour tester les 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);
}
9
répondu Yuli Reiri 2017-01-13 19:33:48

voir ci-dessous pour un exemple;

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

import org.powermock.reflect.Whitebox;

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

Whitebox.invokeMethod(obj, "privateMethod", "param1");
9
répondu Olcay Tarazan 2017-01-13 19:59:42

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 un code avec des méthodes privées ou des champs ou des constructeurs, vous pouvez utiliser BoundBox . C'est exactement ce que vous cherchez. Voici un exemple d'un test qui accède à deux champs privés D'une activité Android pour le tester.:

@UiThreadTest
public void testCompute() {

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

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

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

BoundBox permet de tester facilement des champs privés/protégés, des méthodes et des constructeurs. Vous pouvez même accéder à des choses qui sont cachées par héritage. En effet, BoundBox casse l'encapsulation. Il vous donnera accès à tout cela par réflexion, mais tout est vérifié au moment de la compilation.

il est idéal pour tester du code d'héritage. Utiliser avec précaution. ;)

https://github.com/stephanenicolas/boundbox

9
répondu Snicolas 2017-12-14 12:25:37

tout d'abord, je vais jeter cette question: Pourquoi vos membres privés ont-ils besoin de tests isolés? Sont-ils si complexes, fournissant des comportements si compliqués qu'ils exigent des tests en dehors de la surface publique? C'est un test unitaire, pas un test de ligne de code. Ne pas transpirer les petites choses.

S'ils sont aussi grands, assez grands pour que chacun de ces membres privés soit une "unité" de grande complexité, envisagez de refacturer ces membres privés hors de cette classe.

si le remaniement est inapproprié ou irréalisable, pouvez-vous utiliser le modèle de stratégie pour remplacer l'accès à ces fonctions de membre privé / classes de membre lorsqu'il est soumis au test de l'unité? Dans le test à l'unité, la stratégie fournirait une validation supplémentaire, mais dans les constructions de version, ce serait un simple passage.

7
répondu Aaron 2017-01-13 19:12:04

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

méthodes D'appel, p.ex. private void method(String s) - par réflexion Java

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

l'Appel de méthodes, par exemple private void method(String s) passe-partout

interface Accessible {
  void method(String s);
}

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

réglage champs, p.ex. private BigInteger amount; - par Java reflection

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

zones de réglage, p.ex. 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
répondu CoronA 2017-01-13 19:56:35

Pour Java j'utilise réflexion , car je n'aime pas l'idée de changer l'accès à un package sur la méthode déclarée juste pour le plaisir de tester. Toutefois, Je ne fais généralement que tester les méthodes publiques, ce qui devrait également garantir le bon fonctionnement des méthodes privées.

vous ne pouvez pas utiliser la réflexion pour obtenir des méthodes privées de l'extérieur de la titulaire de la classe, le privé modificateur affecte la réflexion est aussi

Ce n'est pas vrai. Vous le pouvez très certainement, comme indiqué dans réponse de Cem Catikkas .

6
répondu Josh Brown 2017-12-14 12:26:40