2009-05-06 6 views
6

Merci à tous pour votre aide. Un certain nombre d'entre vous ont répondu (comme j'aurais dû le prévoir) des réponses indiquant que toute mon approche était erronée, ou que le code de bas niveau ne devrait jamais avoir à savoir s'il fonctionne ou non dans un conteneur. J'aurais tendance à être d'accord. Cependant, j'ai affaire à une application héritée complexe et je n'ai pas la possibilité d'effectuer un refactoring majeur pour le problème actuel. Permettez-moi de revenir en arrière et poser la question la motivée de ma question initiale.Comment les tests unitaires doivent-ils configurer les sources de données lorsqu'elles ne sont pas exécutées sur un serveur d'applications?

J'ai une application héritée fonctionnant sous JBoss, et j'ai apporté quelques modifications au code de niveau inférieur. J'ai créé un test unitaire pour ma modification. Pour exécuter le test, j'ai besoin de me connecter à une base de données.

Le code existant obtient la source de données de cette façon:

(jndiName est une chaîne définie)

Context ctx = new InitialContext(); 
DataSource dataSource = (DataSource) ctx.lookup(jndiName); 

Mon problème est que quand je lance ce code sous test unitaire, le contexte n'a pas de données sources définies. Ma solution à ceci était d'essayer de voir si je cours sous le serveur d'application et, sinon, créez le DataSource de test et le renvoyez. Si je cours sous le serveur d'application, j'utilise le code ci-dessus.

Donc, mon réel question est: Quelle est la bonne façon de faire cela? Existe-t-il une méthode approuvée permettant au test unitaire de configurer le contexte pour renvoyer la source de données appropriée afin que le code testé ne sache pas où il s'exécute?


pour le contexte: MA QUESTION ORIGINAL:

J'ai un code Java qui a besoin de savoir si oui ou non il est en cours d'exécution sous JBoss. Existe-t-il un moyen canonique pour que le code indique s'il s'exécute dans un conteneur?

Ma première approche a été développée par experimention et consiste à obtenir le contexte initial et à tester qu'il peut rechercher certaines valeurs.

private boolean isRunningUnderJBoss(Context ctx) { 
     boolean runningUnderJBoss = false; 
     try { 
      // The following invokes a naming exception when not running under 
      // JBoss. 
      ctx.getNameInNamespace(); 

      // The URL packages must contain the string "jboss". 
      String urlPackages = (String) ctx.lookup("java.naming.factory.url.pkgs"); 
      if ((urlPackages != null) && (urlPackages.toUpperCase().contains("JBOSS"))) { 
       runningUnderJBoss = true; 
      } 
     } catch (Exception e) { 
      // If we get there, we are not under JBoss 
      runningUnderJBoss = false; 
     } 
     return runningUnderJBoss; 
    } 

Context ctx = new InitialContext(); 
if (isRunningUnderJboss(ctx) 
{ 
......... 

Maintenant, cela semble fonctionner, mais il se sent comme un hack. Quelle est la "bonne" façon de faire cela? Idéalement, je voudrais un moyen qui fonctionne avec une variété de serveurs d'applications, pas seulement JBoss.

+0

Je suppose, plutôt que JBoss, que vous voulez dire Tomcat? (Qui est incorporé dans JBoss.) – Eddie

+0

Essayez-vous de déterminer conteneur vs non-conteneur ou le type de conteneur dans lequel l'application s'exécute? – Kapsh

+1

Pourriez-vous développer pourquoi vous avez besoin de savoir si son fonctionnement dans un conteneur? Cela pourrait aider à orienter les réponses. –

Répondre

2

L'approche entière se sent mal à moi. Si votre application a besoin de savoir dans quel conteneur elle fonctionne, vous faites quelque chose de mal. Quand j'utilise Spring, je peux passer de Tomcat à WebLogic et revenir sans rien changer. Je suis sûr qu'avec une configuration correcte, je pourrais faire le même tour avec JBOSS. C'est l'objectif que je viserais.

+0

Beaucoup de gens ont fait des suggestions utiles, mais cette réponse décrit l'approche que j'ai fini par prendre. J'ai enlevé toute logique dans le code sous test qui essayait de voir si elle était dans l'environnement de test. J'ai ensuite exploré la documentation et compris comment créer mon propre contexte et source de données afin que le code testé ait une source de données différente lorsqu'il est exécuté sous JUnit. Merci à tous ceux qui ont répondu. –

+1

Mark - cela vous dérangerait-il de poster cette configuration? Il serait utile aux autres qui se trouvent dans une situation similaire. –

1

Peut-être quelque chose comme ça (laid, mais il peut travailler)

private void isRunningOn(String thatServerName) { 

    String uniqueClassName = getSpecialClassNameFor(thatServerName); 
    try { 
     Class.forName(uniqueClassName); 
    } catch (ClassNotFoudException cnfe) { 
     return false; 
    } 
    return true; 
} 

La méthode getSpecialClassNameFor retournerait une classe qui est unique pour chaque serveur d'applications (et peut renvoyer de nouveaux noms de classe quand plus applications serveurs sont ajouté)

Ensuite, vous l'utiliser comme:

if(isRunningOn("JBoss")) { 
     createJBossStrategy....etcetc 
    } 
-1

Une manière propre à faire w Soit avoir des écouteurs de cycle de vie configurés en web.xml. Ceux-ci peuvent définir des drapeaux globaux si vous voulez. Par exemple, vous pouvez définir un ServletContextListener dans votre web.xml et dans la méthode contextInitialized, définir un indicateur global que vous exécutez dans un conteneur. Si le drapeau global n'est pas défini, vous n'êtes pas exécuté dans un conteneur.

+0

Depuis quand les drapeaux globaux sont-ils propres? –

+2

Les drapeaux mondiaux ne sont pas toujours mauvais. Trop d'état global indique généralement une mauvaise conception, mais un seul drapeau global est raisonnable pour ce type d'information. Si vous avez une meilleure idée, suggérez-la vous-même. – Eddie

1

Il y a plusieurs façons de résoudre ce problème. La première consiste à passer un objet Context à la classe lorsqu'elle est sous test unitaire. Si vous ne pouvez pas modifier la signature de la méthode, refactorisez la création du contexte initial en une méthode protégée et testez une sous-classe qui renvoie l'objet de contexte simulé en remplaçant la méthode. Cela peut au moins mettre la classe à l'épreuve de sorte que vous puissiez refactoriser de meilleures alternatives à partir de là.

La prochaine option est de faire des connexions de base de données une usine qui peut dire si elle est dans un conteneur ou non, et faire les choses appropriées dans chaque cas. Une chose à penser est - une fois que vous avez cette connexion de base de données hors du conteneur, qu'allez-vous en faire? C'est plus facile, mais ce n'est pas tout à fait un test unitaire si vous devez transporter toute la couche d'accès aux données.

Pour plus d'aide dans cette direction de déplacement de code ancien sous test unitaire, je vous suggère de regarder Michael Feather Working Effectively with Legacy Code.

5

L'ensemble du concept est de retour à l'avant. Le code de niveau inférieur ne devrait pas faire ce genre de test. Si vous avez besoin d'une implémentation différente, transmettez-la à un point pertinent.

+0

Peut-être qu'il essaie de déterminer s'il a besoin d'une implémentation différente en premier lieu. – OscarRyz

+0

Ensuite, il doit placer cela plus haut. –

4

Une combinaison de Dependency Injection (que ce soit par le biais de Spring, de fichiers de configuration ou d'arguments de programme) et du motif Factory fonctionne généralement mieux. Par exemple, je passe un argument à mes scripts Ant qui configurent les fichiers de configuration selon que l'oreille ou la guerre se trouve dans un environnement de développement, de test ou de production.

+0

+1 pour DI, cette approche vous permettrait de passer facilement des objets simulés dans –

+0

convenu. Si vous voulez rester simple, vous pouvez simplement utiliser un pattern de stratégie pour obtenir la source de données lors de l'exécution. Un fichier de configuration déterminerait quelle stratégie utiliser en fonction de l'environnement, un peu comme l'option suggérée dans la réponse. Vous aurez besoin d'une source de données dev, bien sûr, mais c'est assez simple à écrire. – Robin

1
Context ctx = new InitialContext(); 
DataSource dataSource = (DataSource) ctx.lookup(jndiName); 

Qui construit le InitialContext? Sa construction doit être en dehors du code que vous essayez de tester, sinon vous ne serez pas en mesure de se moquer du contexte. Puisque vous avez dit que vous travaillez sur une application héritée, commencez par refactoriser le code afin de pouvoir facilement injecter le contexte ou la source de données dans la classe. Ensuite, vous pouvez plus facilement écrire des tests pour cette classe.

Vous pouvez effectuer la transition du code hérité en ayant deux constructeurs, comme dans le code ci-dessous, jusqu'à ce que vous ayez refactorisé le code qui construit la classe. De cette façon, vous pouvez tester plus facilement Foo et vous pouvez garder le code qui utilise Foo inchangé. Ensuite, vous pouvez refactoriser lentement le code, de sorte que l'ancien constructeur soit complètement supprimé et toutes les dépendances soient injectées. Mais avant de commencer ce refactoring, vous devriez avoir des tests d'intégration pour couvrir votre dos. Sinon, vous ne pouvez pas savoir si même les simples refactorings, tels que le déplacement de la recherche DataSource vers le constructeur, cassent quelque chose. Ensuite, lorsque le code devient meilleur, plus testable, vous pouvez écrire des tests unitaires. (Par définition, si un test touche le système de fichiers, le réseau ou la base de données, il ne s'agit pas d'un test unitaire - c'est un test d'intégration.

L'avantage des tests unitaires est qu'ils s'exécutent rapidement - des centaines ou des milliers par seconde - et sont très ciblés pour tester un seul comportement à la fois. Cela permet de courir ensuite souvent (si vous hésitez à exécuter tous les tests unitaires après avoir changé une ligne, ils courent trop lentement) afin que vous obteniez un retour rapide. Et parce qu'ils sont très ciblés, vous saurez juste en regardant le nom du test qui échoue exactement où dans le code de production le bogue est.

L'avantage des tests d'intégration est qu'ils s'assurent que toutes les pièces sont correctement raccordées. Cela est également important, mais vous ne pouvez pas les exécuter très souvent parce que des choses comme toucher la base de données les rendent très lents. Mais vous devez toujours les exécuter au moins une fois par jour sur votre serveur d'intégration continue.

Questions connexes