Approche 1: Ne testez pas de Méthodes Privées

Tester des Méthodes Privées avec JUnit et SuiteRunner
par Bill Venners
Mai 24, 2004
Résumé

Cet article compare quatre approches différentes pour tester des méthodes privées dans des classes Java.

Ma toute première utilisation de JUnit a été de créer un kit de test de conformité pour l’API ServiceUI. Le but d’un kit de test de conformité est de s’assurer que d’autres implémentations de la même API sont compatibles avec les spécifications de l’API. Étant donné qu’une spécification d’API définit uniquement l’interface publique de l’API, et non l’implémentation de l’API, un test de conformité n’exerce que l’interface publique. En d’autres termes, un test de conformité est un test de « boîte noire ». Il traite l’API testée comme une boîte noire, dont l’interface externe peut être vue, mais dont l’implémentation interne ne peut pas. Un test de conformité d’une API Java n’a donc besoin que d’accéder aux membres publics des packages et des classes testés. Il n’est pas nécessaire d’accéder à des membres au niveau du package, protégés ou privés.

Lorsque j’ai plus tard appliqué JUnit à la tâche d’écrire des tests unitaires réels, par opposition aux tests de conformité, je me suis retrouvé à vouloir écrire des tests en boîte blanche — des tests qui utilisent la connaissance de l’implémentation interne des packages et des classes testées. Alors que je ne voulais tester que des méthodes publiques dans mes tests de conformité, je voulais écrire des tests unitaires pour l’accès aux paquets et occasionnellement des méthodes privées ainsi que des méthodes publiques.

Daniel Steinberg m’a montré la technique courante de JUnit consistant à utiliser des arbres de code source parallèles, ce qui m’a permis de placer les classes de test dans le même package que les classes testées, mais de les conserver dans un répertoire différent. Cela a permis une séparation nette du code de test et du code de production. En plaçant les deux arbres sources dans le CHEMIN de CLASSE, mes classes de test peuvent accéder aux méthodes et aux classes au niveau du package dans le package testé. Cela m’a cependant laissé le problème de tester des méthodes privées.

Lorsque j’ai demandé à Daniel de tester des méthodes privées, il m’a gentiment suggéré de tester indirectement les méthodes privées en testant les méthodes d’accès aux paquets et publiques qui appellent les méthodes privées. Cette réponse ne m’a pas tout à fait satisfait, car à l’occasion, j’ai vraiment ressenti l’envie de tester directement une méthode privée. Ma solution initiale consistait simplement à rendre ces méthodes privées d’accès au package, ce qui m’a permis de les tester directement avec JUnit à partir des classes de test du même package dans l’arborescence source parallèle. Cela a bien fonctionné, mais m’a fait me sentir un peu sale d’une manière ou d’une autre. Bien qu’en général, j’ai découvert que penser à concevoir des interfaces afin qu’elles puissent être facilement testées à l’unité m’a aidé à concevoir de meilleures interfaces, dans ce cas, j’ai senti que je rendais la conception légèrement pire pour la rendre testable.

Quand j’ai fini par participer à la création de ce que Frank Sommers, Matt Gerrans et moi avons finalement publié sous le nom d’Artima SuiteRunner, j’ai juré que je rendrais les tests de méthodes privées plus faciles dans SuiteRunner que dans JUnit. Mais après avoir étudié les différentes approches pour tester des méthodes privées, j’ai décidé de ne rien faire de spécial dans SuiteRunner pour prendre en charge les méthodes de test privées. Donc, que vous utilisiez JUnit ou SuiteRunner, vous disposez des quatre mêmes approches de base pour tester des méthodes privées:

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

Dans cet article, je vais discuter de ces quatre approches pour tester des méthodes privées en Java. Je vais examiner les avantages et les inconvénients de chacun et essayer de faire la lumière sur le moment où il est logique d’utiliser chaque approche.

Comme je l’ai mentionné dans l’introduction, j’ai d’abord entendu le conseil de supprimer mes envies occasionnelles de tester des méthodes privées de Daniel Steinberg. Mais Daniel n’est pas seulement la source de ce conseil que j’ai rencontré. Cela semble être une attitude commune dans la communauté Java. Par exemple, la FAQ JUnit indique:

Tester des méthodes privées peut indiquer que ces méthodes doivent être déplacées dans une autre classe pour promouvoir la réutilisabilité.

Charles Miller a exprimé un point de vue similaire dans son blog:

Si vous disposez d’une suite complète de tests pour l’interface exposée (non privée) d’une classe, ces tests devraient, par nature, vérifier que toute méthode privée au sein de la classe fonctionne également. Si ce n’est pas le cas, ou si vous avez une méthode privée si complexe qu’elle doit être testée hors du contexte de ses appelants publics, je considérerais cela comme une odeur de code.

Et Dave Thomas et Andy Hunt, dans leur livre Pragmatic Unit Testing, écrivent:

En général, vous ne voulez pas casser d’encapsulation pour le plaisir des tests (ou comme disait maman, « n’exposez pas vos soldats! »). La plupart du temps, vous devriez pouvoir tester une classe en exerçant ses méthodes publiques. S’il y a une fonctionnalité importante qui est cachée derrière un accès privé ou protégé, cela pourrait être un signe d’avertissement qu’il y a une autre classe qui a du mal à sortir.

Je crois à tous ces conseils. La plupart du temps, les méthodes privées peuvent être testées plus efficacement via l’approche 1, indirectement en testant les méthodes au niveau du paquet, protégées et publiques qui les appellent. Mais inévitablement, certaines personnes dans certaines situations penseront que tester directement une méthode privée est la bonne chose à faire.

Dans mon cas, j’ai tendance à créer de nombreuses méthodes d’utilité privée. Ces méthodes utilitaires ne font souvent rien avec les données d’instance, elles fonctionnent simplement sur les paramètres passés et renvoient un résultat. Je crée de telles méthodes pour rendre la méthode d’appel plus facile à comprendre. C’est un moyen de gérer la complexité de l’implémentation de la classe. Maintenant, si j’extrait la méthode privée d’une méthode qui fonctionne déjà et a une bonne couverture de test unitaire, alors ces tests unitaires existants suffiront probablement. Je n’ai pas besoin d’écrire plus de tests unitaires uniquement pour la méthode privée. Mais si je veux écrire la méthode privée avant sa méthode d’appel, et que je veux écrire les tests unitaires avant d’écrire la méthode privée, je reviens à vouloir tester directement la méthode privée. Dans le cas des méthodes d’utilité privée, je ne pense pas que mon envie de tester directement les méthodes soit, comme le dit la FAQ de JUnit, « une indication que ces méthodes devraient être déplacées dans une autre classe pour promouvoir la réutilisabilité. »Ces méthodes ne sont vraiment nécessaires que dans la classe dans laquelle elles résident et ne sont en fait souvent appelées que par une autre méthode.

Une autre raison pour laquelle j’ai parfois envie de tester directement des méthodes privées est que j’ai tendance à penser que les tests unitaires m’aident à obtenir un système robuste en construisant ce système à partir de pièces robustes. Chaque partie est une « unité » pour laquelle je peux écrire des « tests unitaires. »Les tests unitaires m’aident à m’assurer que chaque unité fonctionne correctement, ce qui m’aide à construire un système qui fonctionne correctement dans son ensemble. L’unité principale que je pense en termes de programmation en Java est la classe. Je construis des systèmes à partir de classes, et les tests unitaires me donnent l’assurance que mes classes sont robustes. Mais dans une certaine mesure, je ressens également la même chose à propos des méthodes privées à partir desquelles je compose les méthodes d’accès aux paquets, protégées et publiques. Ces méthodes privées sont des unités qui peuvent être testées individuellement. De tels tests unitaires me donnent la confiance que les méthodes privées fonctionnent correctement, ce qui m’aide à construire des méthodes robustes à accès aux paquets, protégées et publiques.

Comme je l’ai mentionné dans l’introduction, donner accès aux paquets de méthodes était ma première approche pour tester des méthodes privées avec JUnit. Cette approche fonctionne très bien, mais elle a un léger coût. Quand je vois un spécificateur d’accès privé sur une méthode, cela me dit quelque chose que j’aime savoir — que cela fait partie de l’implémentation de la classe. Je sais que je peux ignorer la méthode si j’essaie simplement d’utiliser la classe d’une autre classe du package. Je pourrais comprendre cela à propos d’une méthode d’accès aux paquets en examinant de plus près le nom, la documentation et le code de la méthode, mais le mot privé communique de manière beaucoup plus efficace. De plus, le principal problème que j’ai avec cette approche est philosophique. Bien que cela ne me dérange pas de « casser l’encapsulation pour le plaisir des tests », comme le diraient Dave et Andy, je ne me sens tout simplement pas bien de casser l’encapsulation d’une manière qui modifie l’API au niveau du package. En d’autres termes, bien que je sois assez enthousiaste à l’idée de tester des méthodes non publiques de classes, c’est-à-dire de créer des tests unitaires en « boîte blanche », je préférerais que l’API des classes testées, y compris l’API au niveau du package, ne soit pas modifiée pour faciliter ces tests.

Approche 3: Utiliser une classe de test imbriquée

Une troisième approche pour tester des méthodes privées consiste à imbriquer une classe de test statique à l’intérieur de la classe de production testée. Étant donné qu’une classe imbriquée a accès aux membres privés de sa classe englobante, elle pourrait appeler directement les méthodes privées. La classe statique elle-même pourrait être un accès au package, ce qui lui permettrait d’être chargé dans le cadre du test de la boîte blanche.

L’inconvénient de cette approche est que si vous ne voulez pas que la classe de test imbriquée soit accessible dans votre fichier JAR de déploiement, vous devez faire un peu de travail supplémentaire pour l’extraire. De plus, certaines personnes peuvent ne pas aimer que le code de test soit mélangé dans le même fichier que le code de production, bien que d’autres préfèrent cette approche.

Approche 4: Utiliser la réflexion

La quatrième approche pour tester des méthodes privées m’a été suggérée par Vladimir R. Bossicard, qui a écrit des addons JUnit. Un jour au cours du déjeuner, Vladimir m’a informé que l’API java.lang.reflect comprenait des méthodes permettant au code client de contourner le mécanisme de protection d’accès de la machine virtuelle Java. Il m’a également dit que son projet JUnit Addons comprenait une classe, junitx.util.PrivateAccessor, pour aider à utiliser l’API de réflexion dans ce but: écrire des tests unitaires qui manipulent des membres privés des classes testées. La FAQ JUnit indique une classe similaire, appelée PrivilegedAccessor, écrite par Charlie Hubbard et Prashant Dhotke.

Un avantage de l’utilisation de l’approche par réflexion pour tester des méthodes privées est qu’elle fournit une séparation nette du code de test et du code de production. Les tests n’ont pas besoin d’être imbriqués dans la classe testée, comme dans l’approche 3. Au contraire, ils peuvent être placés à côté des autres tests qui exercent les méthodes au niveau du package et publiques de la classe. De plus, vous n’avez pas besoin de modifier l’API de la classe testée. Contrairement à l’approche 2, les méthodes privées peuvent rester privées. Contrairement à l’approche 3, vous n’avez pas besoin d’ajouter de classe imbriquée supplémentaire au niveau de l’accès au package. Le principal inconvénient de cette approche est que le code de test est beaucoup plus verbeux car il utilise l’API de réflexion. De plus, lesEs de refactorisation tels qu’Eclipse et IntelliJ ne sont généralement pas aussi aptes à changer les noms des méthodes où elles sont appelées String passées aux méthodes de l’API de réflexion. Donc, si vous modifiez le nom de la méthode privée avec votre ref de refactorisation, vous devrez peut-être encore apporter quelques modifications à la main dans le code de test.

Un exemple

Pour donner un exemple de méthode privée qui, à mon avis, mérite des tests unitaires directs, j’ai extrait certaines fonctionnalités de la méthodemainde la classeorg.suiterunner.Runner.Runner.mainanalyse les arguments de ligne de commande et exécute une suite de tests, déclenchant éventuellement l’interface graphique. La méthode que j’ai extraite,parseArgsIntoLists, fait partie du travail d’analyse des arguments de ligne de commande à l’application SuiteRunner. Maintenant, pour tester la méthode publique qui appelle cette méthode privée, je devrais testermain. La principale, bien sûr, est l’ensemble de l’application, ce qui rend la méthode assez difficile à tester. En fait, je n’ai aucun test existant pourmain.

À ce stade, vous vous demandez peut-être si j’écrivais d’abord des tests dans le style du développement piloté par les tests, comment ai-je fini par écrire du code d’analyse qui n’avait pas de tests unitaires? La raison principale est que mon infection test est venue par étapes. En fait, j’ai attrapé une grippe de test unitaire bien avant d’avoir entendu parler de JUnit ou de lire le test infecté. À l’époque où je construisais des applications Windows en C ++, par exemple, j’écrivais un peu de code pour tester une méthode nouvellement implémentée, puis j’exécutais ce code et le regardais s’exécuter en parcourant la méthode testée avec le débogueur. Ce type de test unitaire m’a aidé à atteindre la robustesse, mais les tests eux-mêmes n’ont pas vérifié le bon comportement. J’ai vérifié moi-même le bon comportement en observant via le débogueur. Les tests n’étaient pas automatisés, et je ne les ai donc pas enregistrés pour qu’ils puissent être exécutés à nouveau plus tard. Quand j’ai lu Test Infected, j’ai immédiatement vu l’intérêt d’automatiser les tests et de les conserver comme une sorte de test de régression après le refactoring, mais pendant longtemps, cela n’avait pas de sens pour moi d’écrire les tests en premier. Je voulais écrire les tests après avoir implémenté la fonctionnalité, car c’était à ce moment-là que j’avais exécuté les tests avec le débogueur. Une raison secondaire pour laquelle je n’ai pas écrit de tests en premier pendant le développement d’une grande partie de SuiteRunner est que je voulais écrire les tests de SuiteRunner avec SuiteRunner lui-même, dans le but de manger ma propre nourriture pour chiens. Jusqu’à ce que l’API de base de SuiteRunner soit installée, je n’avais pas la boîte à outils de test que je voulais utiliser pour écrire les tests.

Depuis ce temps, cependant, le virus des tests a pris une emprise plus forte sur moi, et je préfère maintenant écrire d’abord des tests unitaires la plupart du temps. Je préfère d’abord écrire des tests, pas tellement parce que je me retrouve avec des conceptions plus propres, ce qui est généralement présenté comme le principal avantage du développement piloté par les tests. Au contraire, je préfère d’abord écrire des tests car je trouve que souvent, si je plonge dans le code sous pression, avec l’intention d’écrire le test plus tard, le test n’est jamais écrit. SuiteRunner lui-même a très peu de tests unitaires à ce stade pour cette raison même. Voici la méthode parseArgsIntoLists:

 private static void parseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) { if (args == null || runpathList == null || reportersList == null || suitesList == null) { throw new NullPointerException(); } for (int i = 0; i < args.length; i++) { if (args.startsWith("-p")) { runpathList.add(args); runpathList.add(args); ++i; } else if (args.startsWith("-g")) { reportersList.add(args); } else if (args.startsWith("-o")) { reportersList.add(args); } else if (args.startsWith("-e")) { reportersList.add(args); } else if (args.startsWith("-f")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-r")) { reportersList.add(args); reportersList.add(args); ++i; } else if (args.startsWith("-s")) { suitesList.add(args); do { ++i; suitesList.add(args); } while (i + 1 < args.length); } else { throw new IllegalArgumentException("Unrecognized argument: " + args); } } }

La ligne de commande de SuiteRunner contient trois types d’informations utilisées par SuiteRunner pour exécuter des tests : runpath, reporters et suites. La méthode parseArgsIntoLists passe simplement par les arguments passés sous la forme d’un tableau de String s et place chaque argument dans l’une des listes, runpathList, reportersList et suitesList.

Avant d’écrire un test pour cette méthode privée, je demanderais si mon envie d’écrire ce test unitaire représente une odeur de code, comme l’a dit Charles Miller dans son blog? Cela indique-t-il que parseArgsIntoLists devrait être déplacé dans une autre classe pour promouvoir la réutilisabilité, comme le suggère la FAQ JUnit? Dave et Andy diraient-ils que c’est un signe avant-coureur qu’il y a une autre classe qui a du mal à sortir? Eh bien, peut-être. Je pourrais facilement créer une classe ArgumentsParser qui ne contient que quelques méthodes statiques qui effectuent le travail d’analyse. La classe ArgumentsParser et les méthodes qu’elle contient pourraient être des accès aux paquets, ce qui les rendrait faciles à tester. Mais ça ne me semble pas juste. Ces méthodes ne sont appelées que par Runner.main. Ils se sentent clairement comme des méthodes privées pour moi. La seule raison pour laquelle je les déplacerais vers une classe ArgumentsParser est de pouvoir les tester. J’utiliserais en fait l’approche numéro 2: rendre l’accès au package des méthodes privées.

Au lieu de cela, pour cet exemple, j’ai décidé de prendre l’approche numéro 4 et d’utiliser la réflexion. J’ai regardé à la fois le junitx.utils.PrivateAccessor de Vladimir Bossicard et le PrivilegedAccessor de Charlie Hubbard et Prashant Dhotke, mais j’ai décidé qu’aucun d’eux ne m’aidait comme je le voulais. D’une part, ces classes ont toutes deux la possibilité de tester des champs pour s’assurer qu’ils sont correctement définis. Jusqu’à présent, je n’ai jamais ressenti le besoin d’accéder directement à des champs privés à partir de tests unitaires. Je veux juste pouvoir tester des méthodes d’utilité privée. Le principal problème que j’ai eu avec ces deux classes, cependant, est de savoir comment elles ont traité les exceptions qui peuvent être levées en essayant d’invoquer la méthode privée via la réflexion. Chaque classe a une ou plusieurs méthodes dont le travail appelle une méthode avec réflexion. Les deux méthodes invokeMethod de PrivilegedAccessor renvoient toute exception à son appelant, y compris trois exceptions vérifiées déclarées dans la clause throws : NoSuchMethodException, IllegalAccessException et InvocationTargetException. En revanche, les deux méthodes invoke de PrivateAccessor attrapent InvocationTargetException, extraient et lancent l’exception cible, l’exception réelle levée par la méthode invoquée. Il attrape ensuite toute autre exception et lance NoSuchMethodException. Je n’aimais pas que l’appelant de PrivilegedAccessor.invokeMethod doive toujours gérer les trois exceptions vérifiées, car je pensais que la façon générale de gérer toute exception serait de laisser le test échouer. J’étais également préoccupé par le fait que PrivateAccessor.invoke rejetait des informations de trace de pile potentiellement utiles dans sa stratégie de gestion des exceptions. Ce que je voulais vraiment, c’était une méthode qui tentait d’invoquer une méthode privée avec réflexion, qui enveloppait toute exception levée en plus de InvocationTargetException dans un TestFailedException non contrôlé. La plupart du temps, cette exception provoquerait l’échec du test. Dans les tests qui s’attendaient à ce qu’une exception soit levée, l’exception contenue dans InvocationTargetException pouvait être extraite et testée pour son exactitude.

Par conséquent, j’ai écrit invokeStaticMethod. L’appel setAccessible(true) est ce qui permet à la méthode privée d’être invoquée depuis l’extérieur de la classe. Une implémentation invokeStaticMethod correspondante à utiliser avec JUnit lancerait AssertionFailedError plutôt que TestFailedException. Voici le code:

 private static void invokeStaticMethod(Class targetClass, String methodName, Class argClasses, Object argObjects) throws InvocationTargetException { try { Method method = targetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); method.invoke(null, argObjects); } catch (NoSuchMethodException e) { // Should happen only rarely, because most times the // specified method should exist. If it does happen, just let // the test fail so the programmer can fix the problem. throw new TestFailedException(e); } catch (SecurityException e) { // Should happen only rarely, because the setAccessible(true) // should be allowed in when running unit tests. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } catch (IllegalAccessException e) { // Should never happen, because setting accessible flag to // true. If setting accessible fails, should throw a security // exception at that point and never get to the invoke. But // just in case, wrap it in a TestFailedException and let a // human figure it out. throw new TestFailedException(e); } catch (IllegalArgumentException e) { // Should happen only rarely, because usually the right // number and types of arguments will be passed. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new TestFailedException(e); } }

Ensuite, j’ai créé une méthode de commodité qui appelle la méthode privée particulière que je voulais tester:

 private static void invokeParseArgsIntoLists(String args, List runpathList, List reportersList, List suitesList) throws InvocationTargetException { // Purposely pass null values to the method, to make sure it throws // NullPointerException Class argClasses = {String.class, List.class, List.class, List.class }; Object argObjects = {args, runpathList, reportersList, suitesList }; invokeStaticMethod(Runner.class, "parseArgsIntoLists", argClasses, argObjects); }

Enfin, je pourrais écrire des tests contre la méthode privée sans trop d’encombrement, comme ceci:

 public void testParseArgsIntoLists() throws InvocationTargetException { String args = new String; List runpathList = new ArrayList(); List reportersList = new ArrayList(); List suitesList = new ArrayList(); try { invokeParseArgsIntoLists(null, runpathList, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, null, reportersList, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, null, suitesList); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } try { invokeParseArgsIntoLists(args, runpathList, reportersList, null); fail(); } catch (InvocationTargetException e) { // throw the InvocationTargetException unless the target // exception is NullPointerException, which is expected Throwable targetException = e.getTargetException(); if (!(targetException instanceof NullPointerException)) { throw e; } } args = new String; args = "-p"; args = "\"mydir\""; args = "-g"; args = "-f"; args = "test.out"; args = "-s"; args = "MySuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 3); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(suitesList.size() == 2); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); args = new String; args = "-p"; args = "\"mydir\""; args = "-e"; args = "-o"; args = "-r"; args = "MyCustomReporter"; args = "-s"; args = "MySuite"; args = "MyOtherSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 4); verify(reportersList.get(0).equals(args)); verify(reportersList.get(1).equals(args)); verify(reportersList.get(2).equals(args)); verify(reportersList.get(3).equals(args)); verify(suitesList.size() == 3); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); args = new String; args = "-p"; args = "\"serviceuitest-1.1beta4.jar myjini http://myhost:9998/myfile.jar\""; args = "-g"; args = "-s"; args = "MySuite"; args = "MySecondSuite"; args = "MyThirdSuite"; args = "MyFourthSuite"; args = "MyFifthSuite"; args = "MySixthSuite"; runpathList.clear(); reportersList.clear(); suitesList.clear(); invokeParseArgsIntoLists(args, runpathList, reportersList, suitesList); verify(runpathList.size() == 2); verify(runpathList.get(0).equals(args)); verify(runpathList.get(1).equals(args)); verify(reportersList.size() == 1); verify(reportersList.get(0).equals(args)); verify(suitesList.size() == 7); verify(suitesList.get(0).equals(args)); verify(suitesList.get(1).equals(args)); verify(suitesList.get(2).equals(args)); verify(suitesList.get(3).equals(args)); verify(suitesList.get(4).equals(args)); verify(suitesList.get(5).equals(args)); verify(suitesList.get(6).equals(args)); }

Conclusion

L’approche 1, tester indirectement des méthodes privées en testant les méthodes au niveau du paquet, protégées et publiques qui les appellent, sera souvent la meilleure approche. Dans les cas où vous souhaitez vraiment tester directement des méthodes privées, l’utilisation de la réflexion pour tester des méthodes privées, bien que plutôt lourde, offre la séparation la plus nette du code de test du code de production et le moins d’impact sur le code de production. Cependant, si cela ne vous dérange pas de créer les méthodes privées particulières que vous souhaitez tester l’accès au package, vous pouvez utiliser l’approche 2. Ou si cela ne vous dérange pas de placer une classe de test imbriquée dans votre classe de production en cours de test, l’approche 3 vous permettrait au moins de garder les méthodes privées privées.

Il n’y a pas de réponse parfaite. Mais si vous adoptez l’approche 4, vous vous retrouverez finalement avec une poignée de méthodes comme invokeStaticMethod que vous pouvez réutiliser. Une fois que vous avez écrit une méthode pratique, comme invokeParseArgsIntoLists, pour une méthode privée, vous pouvez écrire des tests contre la méthode privée sans trop de difficulté.

Ressources

1. L’API ServiceUI définit un moyen standard d’attacher des interfaces utilisateur aux services Jini:
http://www.artima.com/jini/serviceui/index.html

2. Daniel Steinberg est actuellement rédacteur en chef de Java.NET:
http://www.java.net/

3. Artima SuiteRunner est une boîte à outils de test open source gratuite et JUnit runner:
http://www.artima.com/suiterunner/index.html

4.Question FAQ JUnit sur les méthodes de test privées:
http://junit.sourceforge.net/doc/faq/faq.htm#tests_10

5. Tester des méthodes privées (Ne le faites pas), un article du blog de Charles Miller:
http://fishbowl.pastiche.org/2003/03/28/testing_private_methods_dont_do_it

6. Andy Hunt et Dave Thomas sont les auteurs de Pragmatic Unit Testing, disponible au magasin Pragmatic.

7. JUnit Addons est une collection de classes d’assistance pour JUnit créée par Vladimar R. Bossicard:
http://sourceforge.net/projects/junit-addons

PrivateAccessor est la classe des Addons JUnit qui facilite les tests des membres privés:
http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html

9.Classe PrivilegedAccessor, que vous pouvez utiliser pour accéder à des membres privés:
http://groups.yahoo.com/group/junit/files/src/PrivilegedAccessor.java

10. Le développement piloté par les tests par l’exemple, par Kent Beck, décrit la technique test-first:
http://www.amazon.com/exec/obidos/ASIN/0321146530/

11. Test Infected, par Kent Beck et Erich Gamma, a introduit JUnit dans le monde:
http://junit.sourceforge.net/doc/testinfected/testing.htm

Méthodes privées de test unitaire, un article de blog sur nUnit par Ted Graham:
http://weblogs.asp.net/tgraham/archive/2003/12/31/46984.aspx

Subvertir la protection d’accès de Java pour les tests Unitaires, un O’Reilly OnJava.com article de Ross Burton:
http://www.onjava.com/pub/a/onjava/2003/11/12/reflection.html

La classe RunnerSuite, à partir de laquelle les extraits de code de cet article ont été tirés, apparaît dans son intégralité ici:
http://www.artima.com/suiterunner/privateExample.html

Pourquoi Nous Avons Refactorisé JUnit
http://www.artima.com/suiterunner/why.html

Tutoriel Artima SuiteRunner, Conformité des Bâtiments et Tests Unitaires avec Artima SuiteRunner:
http://www.artima.com/suiterunner/tutorial.html

Commencer avec Artima SuiteRunner, Comment exécuter l’Exemple Simple Inclus dans la Distribution:
http://www.artima.com/suiterunner/start.html

Exécuter des tests JUnit avec Artima SuiteRunner, comment utiliser Artima SuiteRunner en tant que runner JUnit pour exécuter vos suites de tests JUnit existantes:
http://www.artima.com/suiterunner/junit.html

Page d’accueil d’Artima SuiteRunner:
http://www.artima.com/suiterunner/index.html

Page de téléchargement d’Artima SuiteRunner (Vous devez vous connecter Artima.com pour télécharger la version):
http://www.artima.com/suiterunner/download.jsp

Le Forum SuiteRunner:
http://www.artima.com/forums/forum.jsp?forum=61

Réponds !

Vous avez un avis ? Les lecteurs ont déjà posté 26 commentaires sur cet article. Pourquoi ne pas ajouter le vôtre?

À propos de l’auteur

Bill Venners est président d’Artima Software, Inc. et rédacteur en chef de Artima.com . Il est l’auteur du livre Inside the Java Virtual Machine, une étude orientée programmeur de l’architecture et des composants internes de la plate-forme Java. Ses chroniques populaires dans le magazine JavaWorld couvraient les internes Java, le design orienté objet et le Jini. Bill est actif dans la communauté Jini depuis sa création. Il a dirigé le projet ServiceUI de la communauté Jini qui a produit l’API ServiceUI. Le ServiceUI est devenu le moyen standard de facto d’associer des interfaces utilisateur aux services Jini, et a été le premier standard de la communauté Jini approuvé via le processus de décision Jini. Bill est également membre élu du Comité de surveillance Technique initial (TOC) de la Communauté Jini et, à ce titre, a contribué à définir le processus de gouvernance de la communauté. Il consacre actuellement l’essentiel de son énergie à la construction Artima.com en une ressource toujours plus utile pour les développeurs.

You might also like

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.