web-dev-qa-db-fra.com

junit & Java: test de méthodes non publiques

JUnit ne testera que les méthodes publiques de ma classe. Comment puis-je effectuer des tests Junit sur ceux qui ne le sont pas (à savoir, privés, protégés)? 

Je peux les tester en n'utilisant pas Junit, mais je me demandais quelle était la méthode standard Junit.

91
jbu

Une école de pensée sur le test unitaire dit que vous ne devriez être capable que de tester des méthodes publiques, car vous ne devriez tester que votre API publique, et qu'en faisant cela, vous devriez couvrir le code avec vos méthodes non publiques. Votre kilométrage peut varier; Je trouve que c'est parfois le cas et parfois pas.

Cela dit, il existe plusieurs façons de tester des méthodes non publiques:

  • Vous pouvez tester les méthodes protected et scope-package en plaçant vos tests unitaires dans le même package que les classes testées. C'est une pratique assez courante.
  • Vous pouvez tester les méthodes protégées à partir de tests unitaires dans un autre package en créant une sous-classe de la classe à tester qui remplace les méthodes que vous souhaitez tester comme publiques et en appelant ces méthodes remplacées avec les méthodes originales avec le mot clé super. En règle générale, cette "sous-classe de test" est une classe interne de la classe JUnit TestCase effectuant les tests. Ceci est un peu plus hacky, à mon avis, mais je l'ai fait.

J'espère que cela t'aides.

86
MattK

Comme beaucoup de problèmes de tests unitaires, le test de méthodes privées est en réalité un problème de conception déguisé. Plutôt que d'essayer de faire quelque chose de délicat pour tester des méthodes privées, lorsque je souhaite écrire des tests pour des méthodes privées, je prends une minute pour me demander: "Comment aurais-je besoin de concevoir cela pour pouvoir le tester à fond avec des méthodes publiques?"

Si cela ne fonctionne pas, JUnitX permet de tester des méthodes privées, bien que je pense qu'il n'est disponible que pour JUnit 3.8.

36
Kent Beck

Lorsque vous écrivez un test JUnit, vous devez effectuer un changement d'esprit subtil: "Je suis un client de ma propre classe maintenant." Cela signifie que private est privé et que vous testez uniquement le comportement observé par le client. 

Si la méthode devait vraiment être privée, je considérerais cela comme un défaut de conception pour la rendre visible uniquement pour des raisons de test. Vous devez pouvoir en déduire le bon fonctionnement en fonction de ce que voit le client.

Au cours des trois années qui se sont écoulées depuis que j'ai écrit ce document, j'ai abordé le problème de façon légèrement différente, en utilisant la réflexion Java.

Le sale petit secret est que vous pouvez tester des méthodes privées dans JUnit comme vous le feriez avec des méthodes publiques, en utilisant la réflexion. Vous pouvez tester le contenu de votre coeur sans toujours les exposer publiquement à vos clients.

25
duffymo

La solution la plus simple consiste à placer les tests JUnit dans le même package (mais dans un répertoire différent) et à utiliser la visibilité par défaut (par exemple, package-private) pour les méthodes.

Une autre approche plus compliquée consiste à utiliser la réflexion pour accéder à des méthodes privées.

9
starblue

Si vous avez une quantité importante de logique enfouie sous relativement peu de points d'entrée "publics", vous enfreignez probablement le principe de responsabilité unique . Si possible, vous voudrez refactoriser le code en plusieurs classes, ce qui conduira finalement à davantage de méthodes "publiques" à partir desquelles tester.

9
marcumka

Voici la méthode "ne devrait probablement pas le faire de cette façon" que tout le monde continue de vous harceler. Je pense cependant qu'il est tout à fait possible qu'il y ait des raisons de procéder ainsi. Le code suivant va accéder à un champ privé, mais le code d'une méthode privée est presque identique.

public void testPrivateField() throws InterruptedException {
    Class<ClassWPrivateField> clazz = ClassWPrivateField.class;
    try {
        Field privateField = clazz.getDeclaredField("nameOfPrivateField");
        privateField.setAccessible(true); // This is the line
        // do stuff
    } catch(NoSuchFieldException nsfe) {
        nsfe.printStackTrace();
        fail();
    } catch(IllegalAccessException iae) {
        iae.printStackTrace();
        fail();
    }

}
8
Cheese Daneish

J'utilise presque toujours Spring dans mes projets Java et, en tant que tel, mes objets sont conçus pour l'injection de dépendance. Ce sont généralement des implémentations assez granulaires d’interfaces publiques assemblées dans le contexte de l’application. En tant que tel, j’ai rarement (voire jamais) besoin de tester des méthodes privées, car la classe elle-même est suffisamment petite pour ne pas poser de problème.

Même lorsque je n'utilise pas Spring, j'ai tendance à adopter les mêmes pratiques consistant à assembler des objets petits et simples dans des abstractions de plus en plus grandes, chacune étant relativement simple mais rendue complexe par les objets agrégés.

D'après mon expérience, le fait de devoir recourir à des méthodes privées de test unitaire est un indicateur que ce que vous souhaitez tester pourrait (et devrait) être simplifié.

Cela étant, si vous ressentez toujours le besoin:

  • Les méthodes protégées peuvent être testées par des sous-classes;
  • Les méthodes privées de package peuvent être testées en plaçant les tests unitaires dans le même package; et
  • Les méthodes privées peuvent être testées par unité en fournissant, par exemple, une méthode de proxy de fabrique privée par package. Pas idéal mais privé signifie privé.
3
cletus

Vous ne testez généralement pas les méthodes privées, car elles ne peuvent (normalement) être testées indirectement que par une autre méthode publique. Lorsque vous testez des méthodes privées et des tests de conduite, ils résultent généralement d'un refactoring par "méthode d'extraction" et sont déjà testés indirectement.

Si vous souhaitez tester une méthode privée avec beaucoup de logique, la meilleure chose à faire est de déplacer ce code dans une autre classe d'une méthode publique. Une fois que vous avez fait cela, la méthode précédente qui utilisait ce code peut simplifier ses tests en offrant les fonctionnalités fournies par un stub ou un simulacre.

3
Spoike

DP4j Jar

Pour tester des méthodes privées, nous devons utiliser la réflexion et sa dirigée dans toutes les réponses.

bien maintenant cette tâche est simplifiée avec l’aide de Dp4j jar.

  • Dp4j analyse votre code et génère automatiquement le code de l'API Reflection pour vous.

  • Ajoutez simplement dp4j.jar à votre CLASSPATH.

  • Dp4j.jar contient des processeurs d'annotation, ils rechercheront dans votre code les méthodes annotées avec l'annotation @Test JUnit.

  • Dp4j analyse le code de ces méthodes et s’il découvre que vous accédez illégalement à des méthodes privées, il remplacera votre référence de méthode privée non valide par un code équivalent utilisant l’API de Reflection de Java.

Plus de détails ici

2
VdeX

Pour reprendre une pensée d’Andy Hunt, même vos méthodes privées doivent avoir un effet secondaire qui vous intéresse. En d’autres termes, elles doivent être appelées à partir d’une méthode publique et effectuer une tâche intéressante qui modifie l’état de votre objet. . Testez ce changement d'état.

Supposons que vous ayez la méthode publique pubMethod et la méthode privée privMethod. Lorsque vous appelez pubMethod, il appelle à son tour privMethod pour effectuer une tâche (éventuellement en analysant une chaîne). PubMethod utilise ensuite cette chaîne analysée pour définir les valeurs des variables membres ou pour influencer sa propre valeur de retour. Testez en recherchant l'effet souhaité sur la valeur de retour de pubMethod ou sur les variables de membre (éventuellement en utilisant des accesseurs pour les obtenir).

2
Wil Doane

J'ai rencontré le même problème, et le "si ça doit être privé, ça devrait probablement être refondu" ne me va pas. 

Supposons que vous ayez une sorte de fonctionnalité que vous souhaitez séparer d’une manière ou d’une autre à l’intérieur de la classe. Par exemple, supposons que j'ai quelque chose comme ceci:

public class HolderOfSomeStrings{

    private List<String> internal_values;

    public List<String> get()
    {
       List<String> out = new ArrayList<String>();
       for (String s:internal_values)
       { 
           out.add(process(s));
       }
      return get;
    }

   private static String process(String input)
   {
     //do something complicated here that other classes shouldn't be interested in
    }

}

Le point ici est que Junit me force à rendre le processus public, ou du moins protégé, ou à le mettre dans sa propre classe d’utilité. Mais si c'est une sorte de logique interne de HolderOfSomeStrings, ce n'est pas du tout clair pour moi - il me semble que cela devrait être privé et le rendre plus visible gomme le code d'une certaine manière. 

2
Steve B.

Utilisez la réflexion ci-dessus pour tester les méthodes privées .. Si vous suivez TDD, nous devrions tester les méthodes privées, car TDD implique qu’il n’y aurait plus de surprises par la suite . Il ne faut donc pas attendre de terminer sa méthode publique pour tester les utilisateurs privés . Et cela aide dans les tests de régression plus granulaires lors de la re-factorisation.

1
gyanendra kushwaha

Vous pouvez utiliser TestNG au lieu de JUnit, qui se fiche de la méthode privée ou publique.

1
Pierre Gardin

Je suis tout à fait d’accord avec @duffymo pour dire que le code doit être testé du point de vue du client (bien qu’il dise qu’il a cessé de penser de cette façon). Cependant, de ce point de vue, privé vs autres ont des significations différentes. Le client d'une méthode privée est la classe elle-même, je préfère donc les tester via l'API externe (publique/protégée par le package). Cependant, les membres protégés et protégés par package étant destinés aux clients externes, je les teste donc avec des contrefaçons qui héritent de la classe propriétaire ou qui résident dans le même package.

0
Cagatay Kalan

En accord avec à peu près tous les posts - vous devriez probablement refactoriser et probablement pas tester privé sauf par le biais de public, je voulais juste ajouter une façon différente de penser à cela ...

Pensez à votre classe elle-même comme une "unité", pas une méthode. Vous testez la classe et vous assurez qu'elle peut conserver un état valide quelle que soit la méthode utilisée pour appeler les méthodes publiques.

L'appel de méthodes privées peut détruire l'encapsulation et invalider les tests.

0
Bill K

Recherchez "PrivateAccessor.invoke". Mon code l'importe de "junitx.util", mais je ne sais pas d'où il vient.

0
Paul Tomblin

Comme une sorte de rejeton de cela, et je ne suis pas sûr de savoir où tout le monde aborde le problème de la "programmation polyglotte", mais les tests Groovy sont exécutables dans Junit et ignorent le problème public/non public. En passant, il a été officiellement classé comme un "bug", mais quand ils ont essayé de le réparer, il y a eu une telle tempête que le problème a été remis à l’origine.

0
mezmo