web-dev-qa-db-fra.com

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

Je travaille sur un projet Java. Je suis nouveau dans les tests unitaires. Quelle est la meilleure façon de tester les méthodes privées dans les classes Java?

212
Vinoth Kumar C M

En règle générale, 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 alors par extension que vos méthodes privées fonctionnent correctement.

257
Adam Lear

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'un autre côté, le fait de factoriser parfois les détails de l'implémentation dans des classes distinctes conduit à des classes avec des interfaces complexes, beaucoup de données passant entre l'ancienne et la nouvelle classe, ou à une conception qui peut bien paraître du point [OOP de vue, mais ne correspond pas aux intuitions provenant du domaine problématique (par exemple, diviser un modèle de tarification en deux parties juste pour éviter de tester des méthodes privées n'est pas très intuitif et peut entraîner des problèmes plus tard lors de la maintenance/extension du code). Vous ne voulez pas avoir de "classes jumelles" qui sont toujours changées ensemble.

Face au choix entre l'encapsulation et la testabilité, je préfère opter pour la seconde. Il est plus important d'avoir le bon code (c'est-à-dire de produire la sortie correcte) qu'une conception Nice OOP qui ne fonctionne pas correctement, car elle n'a pas été testée de manière adéquate. 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 normal d'avoir une dépendance entre les tests et le code qui est testé. Cela signifie que lorsque vous modifiez l'implémentation, vous devrez peut-être modifier vos tests, mais c'est OK - chaque changement de l'implémentation nécessite de tester à nouveau le code, et si les tests doivent être modifiés pour le faire, alors vous le faites il.

En général, une classe peut proposer plusieurs interfaces. Il y a une interface pour les utilisateurs et une interface pour les mainteneurs. Le second peut en exposer davantage pour garantir que le code est correctement testé. Il ne doit pas nécessairement s'agir d'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 toujours, car c'est tellement utile.

122
quant_dev

Les tests des méthodes privées dépendraient de leur complexité; certaines méthodes privées d'une seule ligne ne justifieraient pas vraiment l'effort supplémentaire de test (cela peut aussi être dit des méthodes publiques), mais certaines méthodes privées peuvent être tout aussi complexes que les méthodes publiques et difficiles à tester via l'interface publique.

Ma technique préférée consiste à rendre privé le package de la méthode privée, 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 d'avoir à s'appuyer sur un test de méthode publique pour couvrir toutes les parties de la 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 les tests et en tant que telle, elle ne devrait pas être appelée par d'autres classes.

Les opposants à cette technique soutiennent que cela rompra l'encapsulation et ouvrira 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, je soutiens que tester la logique complexe est plus important que l'encapsulation strict et ne pas utiliser de méthodes privées de package qui sont clairement marquées comme visible uniquement pour les tests doit être la responsabilité des développeurs qui utilisent et modifient la base de code.

Méthode privée avant le test:

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

Package de méthode privée prête à être testée:

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

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

33
Richard

Si vous ne pouvez pas utiliser d'API externes 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);

Cochez Java Tutorial http://docs.Oracle.com/javase/tutorial/reflect/ or Java API http://docs.Oracle.com/javase/7/docs/api/Java/lang/reflect/package-summary.html pour plus d'informations.

Comme @kij l'a cité dans sa réponse, il y a des moments où une solution simple utilisant la réflexion est vraiment bonne pour tester une méthode privée.

15
Gustavo Coelho

Le cas de test unitaire signifie tester l'unité de code. Cela ne signifie pas de 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 trouver des problèmes au niveau de la plus petite unité que de déterminer les problèmes au niveau de l'interface, puis d'essayer de déboguer celui qui ne fonctionnait pas. Par conséquent, le cas de test unitaire doit être testé quelle que soit sa portée. Voici un moyen de tester des 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 en cours de test. Il utilise la réflexion pour l'appeler éventuellement mais fournit un joli emballage autour de lui. ( https://code.google.com/p/jmockit/ )

9
agrawalankur

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

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

Dans Java vous pouvez écrire des tests lui-même dans la classe à tester et vos méthodes de test devraient également pouvoir appeler des méthodes privées. Je n'ai pas vraiment de gros Java expérience, ce n'est donc probablement pas la meilleure pratique.

Merci.

8
Budda

Je protège généralement ces méthodes. Disons que votre classe est dans:

src/main/Java/you/Class.Java

Vous pouvez créer une classe de test comme:

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 gardez ces méthodes des appelants que vous ne vouliez pas.

Notez que cela attend un arbre source de style maven.

8
gyorgyabraham

Si vous avez vraiment besoin de tester une méthode privée, avec Java je veux dire, vous pouvez utiliser fest assert et/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 une chaîne en paramètre et met à jour sa valeur à 'this is cool testing!', Vous pouvez faire 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 du bean (qu'elles soient privées et qu'aucun setter ne soit écrit) par une maquette, et l'utiliser avec Mockito ou tout autre framework de simulation est vraiment cool. La seule chose que vous devez savoir pour le moment (je ne sais pas si ce sera mieux dans les prochaines versions) est le nom du champ/méthode cible que vous souhaitez manipuler et sa signature.

3
kij

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

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.

2
spaceman

Vous pouvez tester facilement des méthodes privées si vous placez vos tests unitaires dans une classe interne sur 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");
    }
}

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

Mes tests sont exécutés depuis 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

1
Roger Keays