Comment testez-vous les méthodes privées?

184

Je travaille sur un projet java. Je suis nouveau aux tests unitaires. Quel est le meilleur moyen de tester à l'unité les méthodes privées dans les classes java?

Vinoth Kumar CM
la source
1
Cochez cette question sur StackOverflow. Quelques techniques sont mentionnées et discutées. Quelle est la meilleure façon de tester des méthodes privées?
Chiron
34
Mon opinion a toujours été que les méthodes privées n'ont pas besoin de tests, vous devriez tester ce qui est disponible. Une méthode publique. Si vous ne pouvez pas casser la méthode publique, est-ce que ce que font les méthodes privées importe vraiment?
Rig
1
Les méthodes publiques et privées doivent être testées. Par conséquent, un pilote de test doit généralement être à l'intérieur de la classe qu'il teste. comme ça .
échange
2
@Rig - +1 - vous devriez pouvoir invoquer tout le comportement requis d'une méthode privée à partir de vos méthodes publiques. Si vous ne pouvez pas le faire, la fonctionnalité ne pourra jamais être invoquée, il est donc inutile de la tester.
James Anderson
Utilisez-le @Jailbreakdepuis le framework Manifold pour accéder directement aux méthodes privées. De cette façon, votre code de test reste sûr et lisible. Surtout, pas de compromis de conception, pas de méthodes de surexposition et de champs à des fins de tests.
Scott

Réponses:

239

En général, vous ne testez pas directement les méthodes privées. Comme ils sont privés, considérez-les comme un détail d'implémentation. Personne ne va jamais appeler l'un d'eux et s'attendre à ce que cela fonctionne d'une manière particulière.

Vous devriez plutôt tester votre interface publique. Si les méthodes qui appellent vos méthodes privées fonctionnent comme prévu, vous supposez par extension que vos méthodes privées fonctionnent correctement.

Adam Lear
la source
39
+1 bazillion. Et si une méthode privée n'est jamais appelée, ne la testez pas, supprimez-la!
Steven A. Lowe
246
Je ne suis pas d'accord. Parfois, une méthode privée n’est qu’un détail d’implémentation, mais elle reste suffisamment complexe pour pouvoir être testée, afin de s’assurer qu’elle fonctionne correctement. L’interface publique peut offrir un niveau d’abstraction trop élevé pour écrire un test qui cible directement cet algorithme. Il n'est pas toujours possible de le factoriser dans une classe séparée, à cause des données partagées. Dans ce cas, je dirais qu'il est correct de tester une méthode privée.
quant_dev
10
Bien sûr, supprimez-le. La seule raison possible pour ne pas supprimer une fonction que vous n'utilisez pas, c'est si vous pensez en avoir besoin ultérieurement, et même dans ce cas, vous devez la supprimer, mais notez la fonction dans vos journaux de validation, ou au moins commentez-la afin que les gens savent que ce sont des choses supplémentaires qu'ils peuvent ignorer.
Jhocking
38
@ quant_dev: Parfois, une classe doit être refactorisée dans plusieurs autres classes. Vos données partagées peuvent également être extraites dans une classe séparée. Dites, une classe "contextuelle". Ensuite, vos deux nouvelles classes peuvent faire référence à la classe de contexte pour leurs données partagées. Cela peut sembler déraisonnable, mais si vos méthodes privées sont suffisamment complexes pour nécessiter des tests individuels, c'est une odeur de code qui indique que votre graphe d'objet doit devenir un peu plus granulaire.
Phil
43
Cette réponse NE répond PAS à la question. La question est de savoir comment les méthodes privées doivent être testées et non si elles doivent être testées. La question de savoir si une méthode privée doit être testée à l'unité est une question intéressante et mérite d'être débattue, mais pas ici . La réponse appropriée consiste à ajouter un commentaire à la question indiquant que le test de méthodes privées peut ne pas être une bonne idée et à créer un lien vers une question distincte qui approfondirait davantage la question.
TallGuy
118

En général, je l'éviterais. Si votre méthode privée est si complexe qu'elle nécessite un test unitaire séparé, cela signifie souvent qu'elle méritait sa propre classe. Cela peut vous encourager à l'écrire de manière réutilisable. Vous devez ensuite tester la nouvelle classe et appeler son interface publique dans votre ancienne classe.

D’autre part, la factorisation des détails d’implémentation dans des classes distinctes aboutit à des classes avec des interfaces complexes, de nombreuses données passant entre l’ancienne et la nouvelle classe, ou à une conception qui peut paraître intéressante du point de vue de la programmation orientée objet, mais qui ne fonctionne pas correctement. faire correspondre les intuitions issues du domaine du problème (par exemple, diviser un modèle de tarification en deux éléments pour éviter de tester des méthodes privées n’est pas très intuitif et peut entraîner des problèmes ultérieurement lors de la maintenance / extension du code). Vous ne voulez pas avoir des "classes jumelles" qui sont toujours modifiées ensemble.

Quand je dois choisir entre encapsulation et testabilité, je préfère la seconde. Il est plus important d’avoir le code correct (c’est-à-dire de produire la sortie correcte) qu’une bonne conception POO qui ne fonctionne pas correctement, car elle n’a pas été testée correctement. En Java, vous pouvez simplement donner à la méthode un accès "par défaut" et placer le test unitaire dans le même package. Les tests unitaires font simplement partie du package que vous développez, et il est correct d'avoir une dépendance entre les tests et le code en cours de test. Cela signifie que lorsque vous modifiez l'implémentation, vous devrez peut-être modifier vos tests, mais ce n'est pas grave - chaque modification de l'implémentation nécessite de tester à nouveau le code, et si les tests doivent être modifiés pour le faire, il vous suffit de le faire. il.

En général, une classe peut proposer plusieurs interfaces. Il existe une interface pour les utilisateurs et une interface pour les responsables. Le second peut en exposer davantage pour garantir que le code est correctement testé. Il n'est pas nécessaire que ce soit un test unitaire sur une méthode privée - il peut s'agir, par exemple, de la journalisation. La journalisation "rompt également l'encapsulation", mais nous le faisons quand même, car c'est très utile.

quant_dev
la source
9
+1 point intéressant. En fin de compte, il s’agit de la maintenabilité, et non du respect des "règles" d’une bonne POO.
Phil
9
+1 Je suis tout à fait d'accord avec la testabilité sur l'encapsulation.
Richard
31

Le test des méthodes privées dépendrait de leur complexité. certaines méthodes privées en une ligne ne nécessiteraient pas réellement un effort supplémentaire de test (on peut aussi en dire autant des méthodes publiques), mais certaines méthodes privées peuvent être aussi complexes que des méthodes publiques et difficiles à tester via l'interface publique.

Ma technique préférée consiste à rendre le package de méthode privé privé, ce qui permettra d'accéder à un test unitaire dans le même package, mais il sera toujours encapsulé à partir de tout autre code. Cela donnera l’avantage de tester directement la logique de la méthode privée au lieu de devoir s’appuyer sur un test de méthode publique pour couvrir toutes les parties d’une logique (éventuellement) complexe.

Si cela est associé à l'annotation @VisibleForTesting dans la bibliothèque Google Guava, vous marquez clairement cette méthode privée du package comme visible uniquement pour le test et, en tant que telle, elle ne devrait pas être appelée par d'autres classes.

Les opposants à cette technique font valoir que cela romprait l'encapsulation et ouvrirait des méthodes privées pour coder dans le même package. Bien que je convienne que cela rompt l’encapsulation et ouvre le code privé à d’autres classes, j’affirme que le test d’une logique complexe est plus important que l’ encapsulation stricte et que le fait de ne pas utiliser de méthodes de package privé clairement marquées comme visibles uniquement pour le test doit incomber aux développeurs. utiliser et changer la base de code.

Méthode privée avant de tester:

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

Méthode privée du paquet prête à être testée:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Remarque: Le fait de placer les tests dans le même package ne revient pas à les placer dans le même dossier physique. La séparation de votre code principal et du code de test dans des structures de dossiers physiques distinctes est une bonne pratique en général, mais cette technique fonctionnera aussi longtemps que les classes sont définies comme dans le même package.

Richard
la source
14

Si vous ne pouvez pas utiliser d'API externe ou si vous ne le souhaitez pas, vous pouvez toujours utiliser l'API JDK standard pure pour accéder aux méthodes privées à l'aide de la réflexion. Voici un exemple

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Consultez le didacticiel Java http://docs.oracle.com/javase/tutorial/reflect/ ou l'API Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html pour plus d'informations.

Comme le mentionnait @kij dans sa réponse, il est parfois utile de tester une méthode privée avec une solution simple utilisant la réflexion.

Gustavo Coelho
la source
9

Cas de test unitaire signifie tester l'unité de code. Cela ne signifie pas tester l'interface, car si vous testez l'interface, cela ne signifie pas que vous testez l'unité de code. Cela devient une sorte de test de boîte noire. En outre, il est préférable de rechercher les problèmes au niveau de l'unité la plus petite que de déterminer les problèmes au niveau de l'interface et d'essayer ensuite de déboguer le composant qui ne fonctionnait pas. Par conséquent, les tests unitaires doivent être testés indépendamment de leur portée. Voici un moyen de tester les méthodes privées.

Si vous utilisez java, vous pouvez utiliser jmockit qui fournit Deencapsulation.invoke pour appeler n’importe quelle méthode privée de la classe sous test. Il utilise la réflexion pour l'appeler éventuellement, mais fournit une belle enveloppe autour de lui. ( https://code.google.com/p/jmockit/ )

Agrawalankur
la source
Comment cela répond-il à la question posée?
Gnat
@gnat, la question était la suivante: quel est le meilleur moyen de tester à l'unité les méthodes privées dans les classes java? Et mon premier point répond à la question.
Agrawalankur
votre premier point fait simplement la publicité d'un outil, mais il n'explique pas pourquoi vous pensez qu'il est meilleur que les alternatives, comme par exemple tester des méthodes privées indirectement comme suggéré et expliqué dans la réponse du haut
gnat
jmockit a déménagé et l'ancienne page officielle pointe vers un article sur "L'urine synthétique et le pipi artificiel". Ce qui est d'ailleurs toujours lié à des choses moqueuses, mais qui n'a pas d'importance ici :) La nouvelle page officielle est: jmockit.github.io
Guillaume
8

Tout d'abord, comme d'autres auteurs l'ont suggéré: réfléchissez-y à deux fois si vous avez vraiment besoin de tester une méthode privée. Et si oui, ...

Dans .NET, vous pouvez le convertir en méthode "Internal" et créer le package " InternalVisible " dans votre projet de test unitaire.

En Java, vous pouvez écrire des tests dans la classe à tester et vos méthodes de test doivent également pouvoir appeler des méthodes privées. Je n'ai pas beaucoup d'expérience Java, ce n'est donc probablement pas la meilleure pratique.

Merci.

Budda
la source
7

Je fais habituellement ces méthodes protégées. Disons que votre classe est en:

src/main/java/you/Class.java

Vous pouvez créer une classe de test en tant que:

src/test/java/you/TestClass.java

Vous avez maintenant accès aux méthodes protégées et pouvez les tester à l'unité (JUnit ou TestNG n'a pas vraiment d'importance), mais vous conservez ces méthodes des appelants que vous ne vouliez pas.

Notez que cela attend une arborescence source de style maven.

gyorgyabraham
la source
3

Si vous avez vraiment besoin de tester une méthode privée, avec Java je veux dire, vous pouvez utiliser fest assertet / ou fest reflect. Il utilise la réflexion.

Importez la bibliothèque avec maven (les versions données ne sont pas les dernières, je pense) ou importez-la directement dans votre chemin de classe:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Par exemple, si vous avez une classe nommée 'MyClass' avec une méthode privée nommée 'myPrivateMethod' qui prend un paramètre String et met à jour sa valeur avec 'this is cool testing!', Vous pouvez effectuer le test Junit suivant:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Cette bibliothèque vous permet également de remplacer toutes les propriétés de haricot (qu'elles soient privées et qu'aucun séparateur ne soit écrit) par un simulacre, et l'utiliser avec Mockito ou tout autre framework simulé est vraiment cool. La seule chose que vous devez savoir pour le moment (je ne sais pas si cela sera meilleur dans les prochaines versions) est le nom du champ / méthode cible que vous souhaitez manipuler, ainsi que sa signature.

kij
la source
2
Bien que la réflexion vous permette de tester des méthodes privées, elle n’est pas bonne pour en détecter les utilisations. Si vous modifiez le nom d'une méthode privée, votre test sera rompu.
Richard
1
Oui, mais c’est un problème mineur de mon point de vue. Bien sûr, je suis tout à fait d’accord avec votre explication ci-dessous sur la visibilité des paquets (avec des classes de tests dans des dossiers séparés mais avec les mêmes paquets), mais parfois vous ne le faites pas. vraiment le choix. Par exemple, si vous avez une très courte mission dans une entreprise qui n'applique pas les "bonnes" méthodes (les classes de test ne sont pas dans le même package, etc.), si vous n'avez pas le temps de refactoriser le code existant sur lequel vous travaillez. / test, c'est encore une bonne alternative.
Kij
2

Ce que je fais normalement en C #, c’est de rendre mes méthodes protégées et non privées. C'est un modificateur d'accès légèrement moins privé, mais il cache la méthode de toutes les classes qui n'héritent pas de la classe sous test.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Toute classe qui n'hérite pas directement de classUnderTest n'a aucune idée que methodToTest existe même. Dans mon code de test, je peux créer une classe de test spéciale qui étend et donne accès à cette méthode ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Cette classe n'existe que dans mon projet de test. Son seul but est de donner accès à cette méthode unique. Cela me permet d'accéder à des endroits que la plupart des autres classes n'ont pas.

astronaute
la source
4
protectedfait partie de l'API publique et présente les mêmes limitations (ne peut jamais être modifié, doit être documenté, ...). En C #, vous pouvez utiliser internalavec InternalsVisibleTo.
CodesInChaos
1

Vous pouvez facilement tester des méthodes privées si vous placez vos tests unitaires dans une classe interne de la classe que vous testez. En utilisant TestNG, vos tests unitaires doivent être des classes internes statiques publiques annotées avec @Test, comme ceci:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Puisqu'il s'agit d'une classe interne, la méthode private peut être appelée.

Mes tests sont exécutés à partir de maven et il trouve automatiquement ces cas de test. Si vous voulez juste tester une classe, vous pouvez le faire

$ mvn test -Dtest=*UserEditorTest

Source: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
la source
4
Vous proposez d'inclure le code de test (et les classes internes supplémentaires) dans le code du package déployé?
1
La classe de test est une classe interne de la classe que vous souhaitez tester. Si vous ne voulez pas que ces classes internes soient déployées, vous pouvez simplement supprimer les fichiers * Test.class de votre paquet.
Roger Keays
1
Je n’aime pas cette option, mais pour beaucoup, c’est probablement une solution valable, qui ne vaut pas les votes négatifs.
Bill K
@RogerKeays: Techniquement, lorsque vous effectuez un déploiement, comment supprimez-vous de telles "classes internes de test"? S'il vous plaît ne dites pas que vous les coupez à la main.
Gyorgyabraham
Je ne les enlève pas. Oui. Je déploie du code qui n'est jamais exécuté au moment de l'exécution. C'est vraiment si mauvais.
Roger Keays