Comment puis-je tester unitaire (en utilisant 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 un lien interne ( static
en C / C ++) ou se trouvant dans un espace de noms privé ( anonyme )?
Il semble mauvais de changer le modificateur d'accès pour une méthode ou une fonction juste pour pouvoir exécuter un test.
java
unit-testing
tdd
Raedwald
la source
la source
Réponses:
Si vous disposez d'une application Java héritée et que vous n'êtes pas autorisé à modifier la visibilité de vos méthodes, la meilleure façon de tester des méthodes privées consiste à utiliser la réflexion .
En interne, nous utilisons des assistants pour obtenir / définir
private
et lesprivate static
variables ainsi que pour appelerprivate
et lesprivate static
méthodes. 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 lesprivate static final
variables par réflexion.Et pour les champs:
la source
La meilleure façon de tester une méthode privée est via une autre méthode publique. Si cela ne peut pas être fait, l'une des conditions suivantes est remplie:
la source
Quand j'ai des méthodes privées dans une classe qui sont suffisamment compliquées pour que je ressens 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 résoudre ces problèmes est de révéler une nouvelle classe qui contient les bits intéressants. Souvent, cette méthode et les champs avec lesquels elle interagit, et peut-être qu'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 classes nouvelles et anciennes sont maintenant plus simples que la classe d'origine, ce qui est génial pour moi (j'ai besoin de garder les choses simples, sinon je me perds!).
Notez que je ne suggère pas que les gens créent des classes sans utiliser leur cerveau! Le point ici est d'utiliser les forces des tests unitaires pour vous aider à trouver de bonnes nouvelles classes.
la source
J'ai utilisé la réflexion pour faire cela pour Java dans le passé, et à mon avis, c'était une grosse erreur.
À strictement parler, vous ne devez pas écrire des tests unitaires qui testent directement des méthodes privées. Ce que vous devriez tester, c'est le contrat public que la classe a avec d'autres objets; vous ne devez jamais tester directement les éléments internes d'un objet. Si un autre développeur souhaite apporter une petite modification interne à la classe, ce qui n'affecte pas le contrat public des classes, il / elle doit alors modifier votre test basé sur la réflexion pour s'assurer qu'il fonctionne. Si vous le faites à plusieurs reprises 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 une gêne pour l'équipe de développement.
Ce que je recommande à la place, c'est d'utiliser un outil de couverture de code tel que Cobertura, pour garantir que les tests unitaires que vous écrivez fournissent une couverture décente du code dans des méthodes privées. De cette façon, vous testez indirectement ce que font les méthodes privées et maintenez un niveau d'agilité plus élevé.
la source
De cet article: Tester des méthodes privées avec JUnit et SuiteRunner (Bill Venners), vous avez essentiellement 4 options:
la source
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 attendez pas à tester explicitement.
la source
Juste deux exemples où je voudrais tester une méthode privée:
SecurityManager
n'est pas configurée pour empêcher cela).Je comprends l'idée de ne tester que le "contrat". Mais je ne vois pas que l'on puisse préconiser de ne pas tester le code - votre kilométrage peut varier.
Mon compromis consiste donc à compliquer les JUnits avec réflexion, plutôt qu'à compromettre ma sécurité et mon SDK.
la source
private
n'est pas la seule alternative. Aucun modificateur d'accès n'estpackage private
et signifie que vous pouvez le tester à l'unité tant que votre test unitaire vit dans le même package.Les méthodes privées sont appelées par une méthode publique, donc les entrées de vos méthodes publiques doivent également tester les méthodes privées qui sont appelées par ces méthodes publiques. Lorsqu'une méthode publique échoue, cela peut être un échec dans la méthode privée.
la source
Une autre approche que j'ai utilisée consiste à changer une méthode privée pour empaqueter privé ou protégé, puis à la compléter avec l' annotation @VisibleForTesting de la bibliothèque Google Guava.
Cela indiquera à quiconque utilisant cette méthode de prendre des précautions et de ne pas y accéder directement, même dans un package. De même, une classe de test n'a pas besoin d'être physiquement dans le même package , mais dans le même package sous le dossier de test .
Par exemple, si une méthode à tester est présente,
src/main/java/mypackage/MyClass.java
votre appel de test doit être placésrc/test/java/mypackage/MyClassTest.java
. De cette façon, vous avez accès à la méthode de test dans votre classe de test.la source
Pour tester du code hérité avec des classes volumineuses et originales, il est souvent très utile de pouvoir tester la seule méthode privée (ou publique) que j'écris en ce moment .
J'utilise le package junitx.util.PrivateAccessor pour Java. Beaucoup de lignes utiles pour accéder aux méthodes privées et aux champs privés.
la source
Class
comme premier paramètre dans ces méthodes, vous n'accédez qu'auxstatic
membres.Dans Spring Framework, vous pouvez tester des méthodes privées à l'aide de cette méthode:
Par exemple:
la source
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 j'ai décrite ici. Cependant, si vous cherchez une alternative à l'utilisation de la réflexion et avez accès à la source que vous testez, ce sera toujours une option.
Il est possible 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 accès aux membres privés et aux méthodes peut tester des zones de code difficiles à cibler spécifiquement avec un accès uniquement aux 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:
Désavantages:
Cependant, si des tests continus nécessitent cette méthode, cela peut être un signal que les méthodes privées doivent être extraites, qui pourraient être testées de manière traditionnelle et publique.
Voici un exemple compliqué de la façon dont cela fonctionnerait:
La classe interne serait compilée pour
ClassToTest$StaticInnerTest
.Voir aussi: Astuce Java 106: classes internes statiques pour le plaisir et le profit
la source
Comme d'autres l'ont dit ... ne testez pas directement les méthodes privées. Voici quelques réflexions:
Exécutez la couverture du code sur les tests unitaires. Si vous voyez que les méthodes ne sont pas entièrement testées, ajoutez aux tests pour augmenter la couverture. Visez une couverture de code à 100%, mais réalisez que vous ne l'obtiendrez probablement pas.
la source
Les méthodes privées sont consommées par les méthodes publiques. Sinon, ce sont des codes morts. C'est pourquoi vous testez la méthode publique, affirmant les résultats attendus de la méthode publique et donc les méthodes privées qu'elle consomme.
Le test des méthodes privées doit être testé par débogage avant d'exécuter vos tests unitaires sur les méthodes publiques.
Ils peuvent également être débogués à l'aide d'un développement piloté par les tests, déboguant vos tests unitaires jusqu'à ce que toutes vos assertions soient satisfaites.
Je pense personnellement qu'il vaut mieux créer des classes en utilisant TDD; créer les talons de méthode publique, puis générer des tests unitaires avec toutes les assertions définies à l'avance, de sorte que le résultat attendu de la méthode soit déterminé avant de la coder. De cette façon, vous n'allez pas dans la mauvaise direction pour que les assertions des tests unitaires correspondent aux résultats. Votre classe est alors robuste et répond aux exigences lorsque tous vos tests unitaires réussissent.
la source
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 setter public indésirable:
la source
Si vous essayez de tester du code existant que vous êtes réticent ou incapable de changer, la réflexion est un bon choix.
Si la conception de la classe est toujours flexible et que vous avez une méthode privée compliquée que vous souhaitez tester séparément, je vous suggère de la retirer dans une classe distincte et de tester cette classe séparément. Cela ne doit pas changer l'interface publique de la classe d'origine; il peut créer en interne une instance de la classe d'assistance et appeler la méthode d'assistance.
Si vous souhaitez tester des conditions d'erreur difficiles provenant de la méthode d'assistance, vous pouvez aller plus loin. Extraire une interface de la classe d'assistance, ajouter un getter et un setter publics à la classe d'origine pour injecter la classe d'assistance (utilisée via son interface), puis injecter une version fictive de la classe d'assistance dans la classe d'origine pour tester le fonctionnement de la classe d'origine répond aux exceptions de l'aide. Cette approche est également utile si vous souhaitez tester la classe d'origine sans également tester la classe d'assistance.
la source
Le test 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).
Alors ne testez pas les méthodes privées.
la source
La réponse de la page FAQ de JUnit.org :
PS Je suis le principal contributeur de Dp4j , demandez- moi si vous avez besoin d'aide. :)
la source
Si vous souhaitez tester des méthodes privées d'une application héritée où vous ne pouvez pas modifier le code, une option pour Java est jMockit , qui vous permettra de créer des simulations sur un objet même lorsqu'elles sont privées pour la classe.
la source
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
J'ai tendance à ne pas tester de méthodes privées. Là réside la folie. Personnellement, je pense que vous ne devriez tester que vos interfaces exposées publiquement (et cela inclut les méthodes protégées et internes).
la source
Si vous utilisez JUnit, jetez un œil aux add-ons junit . Il a la capacité d'ignorer le modèle de sécurité Java et d'accéder aux méthodes et attributs privés.
la source
Je vous suggère de refactoriser un peu votre code. Lorsque vous devez commencer à penser à utiliser la réflexion ou d'autres types de choses, pour simplement tester votre code, quelque chose ne va pas avec votre code.
Vous avez mentionné différents types de problèmes. Commençons par les champs privés. Dans le cas de champs privés, j'aurais ajouté un nouveau constructeur et injecté des champs dans celui-ci. Au lieu de cela:
J'aurais utilisé ceci:
Ce ne sera pas un problème, même avec du code hérité. L'ancien code utilisera un constructeur vide, et si vous me le demandez, le code refactorisé sera plus propre et vous pourrez injecter les valeurs nécessaires dans le test sans réflexion.
Maintenant sur les méthodes privées. D'après mon expérience personnelle, lorsque vous devez écraser une méthode privée pour les tests, cette méthode n'a rien à voir dans cette classe. Un modèle commun, dans ce cas, serait de l' envelopper dans une interface, comme
Callable
et vous passez ensuite cette interface également dans le constructeur (avec cette astuce de constructeur multiple):La plupart du temps, tout ce que j'ai écrit ressemble à un schéma d'injection de dépendance. D'après mon expérience personnelle, c'est vraiment utile lors des tests, et je pense que ce type de code est plus propre et sera plus facile à maintenir. Je dirais la même chose des classes imbriquées. Si une classe imbriquée contient une logique lourde, il serait préférable de la déplacer en tant que classe privée de package et de l'injecter dans une classe qui en a besoin.
Il existe également plusieurs autres modèles de conception que j'ai utilisés lors de la refactorisation et de la maintenance du code hérité, mais tout dépend des cas de votre code à tester. L'utilisation de la réflexion n'est généralement pas un problème, mais lorsque vous avez une application d'entreprise qui est fortement testée et que les tests sont exécutés avant chaque déploiement, tout devient vraiment lent (c'est juste ennuyeux et je n'aime pas ce genre de choses).
Il y a aussi l'injection de setter, mais je ne recommanderais pas de l'utiliser. Je préfère m'en tenir à un constructeur et initialiser tout quand c'est vraiment nécessaire, en laissant la possibilité d'injecter les dépendances nécessaires.
la source
ClassToTest(Callable)
injection. Cela rendClassToTest
plus compliqué. Rester simple. En outre, cela nécessite alors que quelqu'un d'autre diseClassToTest
quelque chose quiClassToTest
devrait être le patron de. Il y a une place pour injecter de la logique, mais ce n'est pas ça. Vous venez de rendre la classe plus difficile à maintenir, pas plus facile.X
n'augmente pas laX
complexité au point où cela pourrait causer plus de problèmes ... et donc nécessiter des tests supplémentaires ... ce qui si vous avez implémenté d'une manière qui peut causer plus de problèmes .. (ce n'est pas une boucle infinie; chaque itération est probablement plus petite que la précédente, mais toujours ennuyeuse)ClassToTest
plus difficile à maintenir? En fait, cela facilite la maintenance de votre application, que suggérez-vous de créer de nouvelles classes pour chaque valeur différente dont vous aurez besoinfirst
et de «secondes» variables?class A{ A(){} f(){} }
est plus simple queclass A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
. Si ce n'était pas vrai, et s'il était vrai que fournir la logique d'une classe comme arguments de constructeur était plus facile à maintenir, alors nous passerions toute la logique comme arguments de constructeur. Je ne vois tout simplement pas comment vous pouvez prétendreclass B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
rendre A plus facile à entretenir. Il y a des cas où l'injection comme ça a du sens, mais je ne vois pas votre exemple comme "plus facile à maintenir"Une méthode privée n'est accessible que dans la même classe. Il n'y a donc aucun moyen de tester une méthode «privée» d'une classe cible à partir d'une classe de test. Une solution consiste à effectuer manuellement des tests unitaires ou à changer votre méthode de «privé» à «protégé».
Et puis une méthode protégée n'est accessible que dans le même package où la classe est définie. Donc, tester une méthode protégée d'une classe cible signifie que nous devons définir votre classe de test dans le même package que la classe cible.
Si tout ce qui précède ne correspond pas à vos besoins, utilisez la méthode de réflexion pour accéder à la méthode privée.
la source
Comme beaucoup l'ont suggéré ci-dessus, un bon moyen est de 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 fait exécutées à partir de vos tests.
la source
Voici ma fonction générique pour tester des champs privés:
la source
Veuillez voir ci-dessous pour un exemple;
La déclaration d'importation suivante doit être ajoutée:
Vous pouvez maintenant passer directement l'objet qui a la méthode privée, le nom de la méthode à appeler et des paramètres supplémentaires comme ci-dessous.
la source
Aujourd'hui, j'ai poussé une bibliothèque Java pour aider à tester des méthodes et des champs privés. Il a été conçu avec Android à l'esprit, mais il peut vraiment être utilisé pour n'importe quel projet Java.
Si vous avez obtenu du code avec des méthodes ou des champs ou des constructeurs privés, 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 la tester:
BoundBox facilite le test de 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. Il vous donnera accès à tout cela grâce à la réflexion, MAIS tout est vérifié au moment de la compilation.
Il est idéal pour tester du code hérité. Utilisez-le soigneusement. ;)
https://github.com/stephanenicolas/boundbox
la source
Tout d'abord, je vais jeter cette question: pourquoi vos membres privés ont-ils besoin de tests isolés? Sont-ils si complexes, offrant des comportements compliqués au point de nécessiter des tests en dehors de la surface publique? Ce sont des tests unitaires, pas des tests de «ligne de code». Ne transpirez pas les petites choses.
S'ils sont si grands, assez grands pour que ces membres privés soient chacun une «unité» de grande complexité - pensez à refactoriser ces membres privés de cette classe.
Si la refactorisation est inappropriée ou irréalisable, pouvez-vous utiliser le modèle de stratégie pour remplacer l'accès à ces fonctions / classes membres privées lors d'un test unitaire? Dans le cadre d'un test unitaire, la stratégie fournirait une validation supplémentaire, mais dans les versions de version, ce serait un simple passthrough.
la source
J'ai récemment eu ce problème et j'ai é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 JavaMéthodes d'appel, par exemple
private void method(String s)
- par PicklockDéfinition de champs, par exemple
private BigInteger amount;
- par réflexion JavaDéfinition de champs, par exemple
private BigInteger amount;
- par Picklockla source
PowerMockito est fait pour cela. Utiliser la dépendance maven
Ensuite, vous pouvez faire
la source