2010-01-11 6 views
7

J'ai commencé à utiliser Guice pour effectuer des injections de dépendances sur un projet, principalement parce que j'ai besoin d'injecter des couches (en utilisant JMock actuellement) à une couche du test unitaire, ce qui rend l'injection manuelle très difficile.Quelle est la meilleure façon d'utiliser Guice et JMock ensemble?

Ma question est quelle est la meilleure approche pour introduire un simulacre? Ce que j'ai actuellement est de faire un nouveau module dans le test unitaire qui satisfait les dépendances et les lier avec un fournisseur qui ressemble à ceci:

public class JMockProvider<T> implements Provider<T> { 
    private T mock; 

    public JMockProvider(T mock) { 
     this.mock = mock; 
    } 

    public T get() { 
     return mock; 
    } 
} 

En passant la maquette dans le constructeur, donc une configuration JMock pourrait ressembler ceci:

final CommunicationQueue queue = context.mock(CommunicationQueue.class); 
    final TransactionRollBack trans = context.mock(TransactionRollBack.class); 
    Injector injector = Guice.createInjector(new AbstractModule() { 
     @Override 
     protected void configure() { 
      bind(CommunicationQueue.class).toProvider(new JMockProvider<QuickBooksCommunicationQueue>(queue)); 
      bind(TransactionRollBack.class).toProvider(new JMockProvider<TransactionRollBack>(trans)); 
     } 
    }); 
    context.checking(new Expectations() {{ 
     oneOf(queue).retrieve(with(any(int.class))); 
     will(returnValue(null)); 
     never(trans); 
    }}); 
    injector.getInstance(RunResponse.class).processResponseImpl(-1); 

Y a-t-il un meilleur moyen? Je sais qu'AtUnit essaye de résoudre ce problème, bien qu'il me manque comment il injecte magiquement un faux qui a été créé localement comme ci-dessus, mais je cherche une raison convaincante pourquoi AtUnit est la bonne réponse ici (autre que sa capacité à changer les cadres DI et moqueurs sans changer les tests) ou s'il y a une meilleure solution pour le faire à la main.

Répondre

12

Vous ne devriez pas avoir besoin d'injecter des mocks à travers un cadre DI. J'utilise Guice et JMock avec beaucoup de succès et mes tests unitaires ne font référence à aucun élément lié à Guice. Je n'utilise que des simulacres et passe en null le cas échéant. DI permettra l'injection et la construction des dépendances de la classe actuelle, donc si vous voulez ajouter une classe mockée (qui arrête effectivement le graphe de dépendance) il vous suffit de le passer. Misko Hevery a déclaré dans un de la Google Tech Talks que les tests unitaires devraient être jonchées de new et null parce que leur portée est localisée à la méthode de test unitaire individuelle - Je suis d'accord avec lui.

Existe-t-il une raison d'utiliser Guice dans les tests, à savoir s'il ne s'agit pas de tests fonctionnels/d'intégration?

Le test ne fonctionnerait-il pas s'il excluait l'injecteur? Ne pourriez-vous factoriser votre test à quelque chose comme:

final CommunicationQueue queue = context.mock(CommunicationQueue.class); 
final TransactionRollBack trans = context.mock(TransactionRollBack.class); 

context.checking(new Expectations() {{ 
    oneOf(queue).retrieve(with(any(int.class))); 
    will(returnValue(null)); 
    never(trans); 
}}); 

RunResponse r = new RunResponse(queue, trans); // depending on the order 
r.processResponseImpl(-1); 
+0

Dans l'exemple spécifique, vous avez raison de dire que DI n'est pas nécessaire pour se moquer (si j'ai rendu le paquet constructeur privé, je suppose), mais d'ici j'ai des dépendances qui doivent être injectées à deux pas du code de test (RunResponse obtient un nom de classe hors de la file et l'instancie), donc je regardais Guice pour faire une injection sur le terrain dans ce but. – Yishai

+4

Je vois. Rien ne vous empêche d'utiliser Guice dans les tests, mais si possible, je suggère d'essayer de l'éviter (sauf si vous testez sur un niveau fonctionnel/d'intégration) Le problème avec cela est que vous violez la loi de Demeter . Votre classe actuelle devrait seulement s'inquiéter de ses dépendances immédiates et les dépendances transitives ne devraient pas être prises en compte. Dans les tests unitaires pour les dépendances de classes actuelles, vous faites exactement la même chose et ne considérez que les dépendances immédiates.Un tel modèle rend votre code plus propre et plus sympathique DI :) – gpampara

1

D'après ce que vous avez dit, il semble que vous ne faites pas une véritable unité de test. Lorsque vous effectuez un test d'unité pour une classe, vous vous concentrez uniquement sur une seule classe. Lorsque vous exécutez votre test unitaire, l'exécution doit uniquement exercer la classe sujet à tester et éviter d'exécuter des méthodes à partir de classes connexes. JMock/Easymock sont des outils pour vous aider à tester votre classe à l'unité. Ils ne sont pas utiles dans le test d'intégration. Dans le contexte du test unitaire, Guice se chevauche avec JMock/Easymock. Guice force les utilisateurs à polluer leur code avec toutes sortes d'annotations (comme @Inject) pour obtenir le même résultat que JMock/Easymock. Je suis vraiment en désaccord avec Guice dans la façon dont ils aident au test. Si les développeurs utilisent eux-mêmes Guice au lieu de JMock/Easymock, ils ne font pas vraiment de véritable test unitaire ou essaient de tester les classes de contrôleurs. La classe de contrôleur de test ne doit pas être testée au niveau de l'unité de test, mais au niveau du test d'intégration. Pour tester l'intégration, l'utilisation de Jakarta Cactus est très utile. Au test d'intégration, les développeurs doivent avoir toutes les dépendances disponibles et, par conséquent, il n'est pas nécessaire d'utiliser Guice. En conclusion, je ne vois aucune raison d'utiliser Guice pour tester un produit. Google Guice peut être utile dans différents contextes mais en testant.

+0

Merci Thang, qui est essentiellement où je suis maintenant, dans la plupart des cas, je n'utilise pas Guice pour les tests. J'ai trouvé Guice pour conduire un meilleur design, donc c'est définitivement gagner sa tenue. Cependant, dans mon cas, le principe du «test unitaire ne teste qu'une classe» n'est pas possible, car une classe doit conduire à en instancier une autre à travers un cadre. C'est compliqué pourquoi c'est le cas, mais il n'y a pas de bon moyen (comme d'une manière sans autres inconvénients qui affectent la robustesse de production) autour de lui - j'ai essayé. – Yishai

+0

Jakarta Cactus a été mis à la retraite: http://jakarta.apache.org/cactus –

0

Yishai, je dois corriger que conduire une meilleure conception doit être crédité au modèle d'injection de dépendance introduit par Martin Fowler. Guice est un outil pour aider si vos codes sont déjà suivis DI modèle. Guice n'apporte rien à votre meilleur codage. Guice résout l'ID de manière ad-hoc (au niveau de l'annotation), donc il ne doit pas non plus être considéré comme un cadre DI. C'est la grande différence entre un conteneur DI basé sur xml par Spring et un conteneur DI basé sur des annotations par Guice.

J'ai passé un peu de temps à lire votre code affiché au début (je ne le fais habituellement pas) juste pour comprendre pourquoi vous avez dit que votre test de classe n'est pas possible. Je comprends enfin le problème auquel vous êtes confronté. D'après ce que je vois au niveau du test unitaire, vous avez tenté d'imiter le flux d'exécution du morceau de code lorsqu'il s'exécute dans le serveur Java EE. C'est la raison pour laquelle tu t'es coincé. Je ne sais pas quelle est l'incitation ici, mais je crains que votre effort de préparation de toutes ces dépendances environnementales va être gaspillé :(

L'écriture du test unitaire consiste à tester une «logique métier» indépendante de quelle que soit la technologie que vous utilisez. Il pourrait y avoir une obligation de gel indiquant que cette logique d'entreprise doit être exécutée dans qualité de service (QoS) de l'environnement. cette exigence est « exigence technique ». Dans votre exemple, CommunicationQueue et TransactionRollBack fonctionnent principalement pour satisfaire aux "exigences techniques", c'est-à-dire que votre "exigence métier" est entourée par toutes ces unités environnementales. ode afin que votre "exigence métier" soit dans des méthodes séparées qui n'ont pas de dépendances. Ensuite, vous écrivez un test d'unité pour ces méthodes.

Je considère toujours que vous faites des tests fonctionnels. Faire des tests fonctionnels au niveau de l'unité de test prend beaucoup de temps et cela ne vaut pas la peine de le faire non plus. J'espère que ça aide

+0

En ce qui concerne Guice, je pense que l'approche de l'annotation est meilleure pour des raisons d'amélioration du contrôle statique et de réduction de la duplication, mais je suis d'accord que Guice est un outil vers un concept de design. En ce qui concerne pourquoi j'ai besoin de cela, il est trop long pour entrer dans un commentaire, mais en très peu de temps, le code exige que le conteneur soit au milieu, donc j'essaie de tester le code sans le conteneur (appelez un côté pour récupérer le résultat de l'autre), mais ils ont des dépendances différentes, doivent être dans des classes différentes, et chacun doit être instancié via le code de l'infrastructure. – Yishai

0

Yishai, on dirait que vous avez besoin d'AOP pour contourner ces dépendances. AspectJ et AspectWerkz sont deux ressources utiles. Cependant, AspectWekz a une très belle fonctionnalité de classes d'aspects de tissage à l'exécution en utilisant la technologie JBS hotswap. Bien sûr, ces efforts d'ingénierie ne devraient s'appliquer qu'au niveau des tests unitaires.

J'ai fait beaucoup d'efforts pour résoudre les dépendances lors des tests unitaires. Des outils tels que JMock, EasyMock, micro-conteneur Java EE, AspectJ, AspectWerkz, Spring IoC peuvent être utilisés pour résoudre le problème DI. Aucun de ces outils n'incite les développeurs à prendre conscience des tests dans leurs codes de production. Dommage que Guice soit en train de violer le concept de garder vos codes propres de toute sorte de sensibilisation aux tests. Et je ne compte pas comme un outil à utiliser dans mon test d'unité.

J'espère vous avoir donné suffisamment de raisons pour supprimer l'annotation @Inject de votre code de production.

+0

Thang, je ne sais pas pourquoi vous pensez que Guice rend votre code plus sensible aux tests que Spring, AspectJ ou AspectWekz mais vous ne m'avez donné aucune raison, juste des alternatives et des assertions. – Yishai

+0

AspectJ & AspectWekz prennent le problème DI au niveau du bytecode. AspectJ utilise le compilateur 'ajc' pour injecter un comportement d'aspect dans les classes bytecode. AspectWekz va plus loin sans avoir besoin d'ajc en utilisant la technologie hotswap bytecode. Ces deux outils fonctionnent au moment de la pré-compilation, de la compilation ou du chargement de la classe pour effectuer l'injection. Spring travaille un peu plus haut niveau en utilisant DI basé sur xml. Bien sûr, votre projet doit déjà adopter Spring pour prendre cet avantage (comme obtenir une instance via les usines) – Thang

Questions connexes