unit-testing - tbd - test driven development java




Comment tester une fonction privée ou une classe comportant des méthodes privées, des champs ou des classes internes? (20)

Comment puis-je tester (avec 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 une liaison interne ( static en C / C ++) ou dans un espace de noms privé ( anonymous )?

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


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 je viens de décrire. Toutefois, si vous recherchez une alternative à l'utilisation de la réflexion et que vous avez accès à la source que vous testez, cette option sera toujours disponible.

Il est potentiellement intéressant 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 un accès à des membres privés et à des méthodes peut tester des zones de code difficiles à cibler spécifiquement avec un accès uniquement à des 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 à gérer
  • 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 les tests continus nécessitent cette méthode, cela peut indiquer que les méthodes privées doivent être extraites, ce qui pourrait être testé 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 dans ClassToTest$StaticInnerTest .

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


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

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

Exécutez la couverture de code sur les tests unitaires. Si vous constatez que les méthodes ne sont pas entièrement testées, ajoutez des tests pour obtenir la couverture. Visez une couverture de code à 100%, mais sachez que vous ne l'obtiendrez probablement pas.


Extrait de cet article: Test de méthodes privées avec JUnit et SuiteRunner (Bill Venners), vous avez essentiellement 4 options:

  • Ne testez pas les méthodes privées.
  • Donne accès au package de méthodes.
  • Utilisez une classe de test imbriquée.
  • Utilisez la réflexion.

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 attendriez pas à tester explicitement.


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

À proprement parler, vous ne devriez pas écrire de tests unitaires testant directement des méthodes privées. Ce que vous devriez tester est le contrat public que la classe a avec d’autres objets; vous ne devriez jamais tester directement les éléments internes d'un objet. Si un autre développeur souhaite apporter un léger changement interne à la classe, qui n'affecte pas le contrat public de la classe, il doit alors modifier votre test basé sur la réflexion pour s'assurer de son bon fonctionnement. Si vous faites cela de manière répétée 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 un désagrément à l'équipe de développement.

À la place, je vous recommande d'utiliser un outil de couverture de code tel que Cobertura, pour vous assurer que les tests unitaires que vous écrivez fournissent une couverture décente du code dans les méthodes privées. De cette manière, vous testez indirectement ce que font les méthodes privées et maintenez un niveau d'agilité plus élevé.


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

  1. Procédures de déchiffrement - Je ne voudrais pas les rendre visibles à qui que ce soit dans le seul but de tester, sinon tout le monde peut les utiliser pour les 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 des méthodes privées dans la plupart des cas, lorsque SecurityManager n'est pas configuré pour empêcher cela).
  2. Création d'un SDK pour la consommation de la communauté. Ici, public prend un sens totalement différent, puisqu'il s'agit d'un code que le monde entier peut voir (et pas seulement interne à 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 réside la fonctionnalité de mon SDK.

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

Mon compromis consiste donc à compliquer les JUnits par la réflexion, plutôt que de compromettre ma sécurité et mon SDK.


Le meilleur moyen de tester une méthode privée consiste à utiliser 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 un 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

Les méthodes privées sont appelées par une méthode publique. Par conséquent, les entrées de vos méthodes publiques doivent également tester les méthodes privées appelées par ces méthodes publiques. Lorsqu'une méthode publique échoue, il peut s'agir d'un échec de la méthode privée.


Lorsque des méthodes privées sont suffisamment compliquées dans une classe pour que je ressente 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 traiter de tels problèmes est de démêler une nouvelle classe qui contient les bits intéressants. Souvent, cette méthode et les champs avec lesquels elle interagit et éventuellement 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 nouvelles et les anciennes classes sont désormais plus simples que la classe d'origine, ce qui est très bien pour moi (je dois garder les choses simples, sinon je me perds!).

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


Pour tester le code existant avec des classes volumineuses et insolites, il est souvent très utile de pouvoir tester la méthode privée (ou publique) que j'écris actuellement .

J'utilise le junitx.util.PrivateAccessor -package for Java. Beaucoup de monolignes 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);

Si vous possédez une application Java héritée et que vous n'êtes pas autorisé à modifier la visibilité de vos méthodes, le meilleur moyen de tester des méthodes privées est d'utiliser la reflection .

En interne, nous utilisons des aides pour obtenir / définir private variables private static private et private static ainsi que pour appeler private méthodes private static private et private static . 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 private static final variables private static final 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 private méthodes private . La même chose s'applique à getDeclaredField .
2. Le setAccessible(true) est requis pour jouer avec les membres privés.


Si vous souhaitez tester les méthodes privées d'une application héritée dans laquelle vous ne pouvez pas modifier le code, l'une des options de Java est jMockit , ce qui vous permet de créer des répliques à un objet même s'il est privé pour la classe.


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 passeur public indésirable:

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

Tester 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).

Donc, ne testez pas les méthodes privées.


Comme beaucoup de personnes ci-dessus l'ont suggéré, un bon moyen consiste à 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 cours d'exécution à partir de vos tests.


S'il vous plaît voir ci-dessous pour un exemple;

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

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

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

@UiThreadTest
public void testCompute() {

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

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

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

Aujourd'hui, j'ai mis en place une bibliothèque Java pour aider à tester les méthodes et les champs privés. Il a été conçu avec Android en tête, mais il peut vraiment être utilisé pour tout projet Java.

Si vous avez du code avec des méthodes privées, des champs ou des constructeurs, 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 le tester:

import org.powermock.reflect.Whitebox;

BoundBox facilite le test des 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. Cela vous donnera accès à tout cela par réflexion, MAIS tout est vérifié au moment de la compilation.

C'est idéal pour tester du code hérité. Utilisez-le avec précaution. ;)

BoundBox


J'ai récemment eu ce problème et é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 des champs, par exemple private BigInteger amount;- par réflexion Java

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

Réglage des 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));

Pour Java, j'utiliserais la reflection car je n'aime pas l'idée de changer l'accès à un paquet sur la méthode déclarée uniquement pour tester. Cependant, je ne teste généralement que les méthodes publiques, ce qui devrait également garantir que les méthodes privées fonctionnent correctement.

vous ne pouvez pas utiliser la réflexion pour obtenir des méthodes privées en dehors de la classe propriétaire, le modificateur privé affecte également la réflexion

Ce n'est pas vrai. Vous le pouvez certainement, comme mentionné dans la réponse de Cem Catikkas .


Tout d’abord, je vais poser cette question: pourquoi vos députés ont-ils besoin de tests isolés? Sont-ils si complexes, fournissant des comportements si compliqués qu’ils nécessitent des tests en dehors de la surface publique? Ce sont des tests unitaires et non des tests de «ligne de code». Ne pas transpirer les petites choses.

S'ils sont si grands, suffisamment grands pour que ces membres privés forment chacun une «unité» de grande complexité, envisagez de reformer ces membres privés en dehors de cette classe.

Si la refactorisation est inappropriée ou infaisable, pouvez-vous utiliser le modèle de stratégie pour remplacer l'accès à ces fonctions membres privées / classes membres lorsque vous êtes sous test unitaire? Sous test unitaire, la stratégie fournirait une validation supplémentaire, mais dans les versions de version, ce serait simple.





tdd