2008-10-30 6 views
8

J'ai le code existant suivant:refactorisation méthode/champ statique statique pour les tests

public class MyLegacyClass 
{ 
    private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource" 

    public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj) 
    { 
     // do stuff using jndiName 
    } 
} 

Cette classe fonctionne dans un J2EE Container.

Maintenant, je voudrais tester la classe à l'extérieur du conteneur.

Quelle est la meilleure stratégie? Le refactoring est fondamentalement autorisé.

L'accès à LegacyDataSource est autorisé (le test ne doit pas nécessairement être un test unitaire "pur").

EDIT: L'introduction de frameworks d'exécution supplémentaires n'est pas autorisée.

+0

Je mis à jour ma réponse en fonction de votre nouvelle restriction. Nous avons en fait un système qui a dû résoudre le même problème. – Robin

Répondre

7

Juste pour faire @ suggestion de Robin d'un modèle de stratégie plus concrète: (Notez que l'API publique de votre question initiale reste inchangée.)

public class MyLegacyClass { 

    private static Strategy strategy = new JNDIStrategy(); 

    public static SomeLegacyClass doSomeLegacyStuff(SomeOtherLegacyClass legacyObj) { 
    // legacy logic 
    SomeLegacyClass result = strategy.doSomeStuff(legacyObj); 
    // more legacy logic 
    return result; 
    } 

    static void setStrategy(Strategy strategy){ 
    MyLegacyClass.strategy = strategy; 
    } 

} 

interface Strategy{ 
    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj); 
} 

class JNDIStrategy implements Strategy { 
    private static final String jndiName = "java:comp/env/jdbc/LegacyDataSource"; 

    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) { 
    // do stuff using jndiName 
    } 
} 

... et test JUnit. Je ne suis pas un grand fan de devoir faire cette configuration/démontage de maintenance, mais c'est un effet secondaire regrettable d'avoir une API basée sur des méthodes statiques (ou Singletons d'ailleurs). Ce que je faire comme à propos de ce test est qu'il n'utilise pas JNDI - c'est bon parce que (a) il va courir vite, et (b) le test unitaire ne devrait tester la logique métier dans doSomeLegacyStuff() méthode de toute façon, pas tester la source de données réelle. (Soit dit en passant, cela suppose la classe de test est dans le même paquet que MyLegacyClass.)

public class MyLegacyClassTest extends TestCase { 

    private MockStrategy mockStrategy = new MockStrategy(); 

    protected void setUp() throws Exception { 
    MyLegacyClass.setStrategy(mockStrategy); 
    } 

    protected void tearDown() throws Exception { 
    // TODO, reset original strategy on MyLegacyClass... 
    } 

    public void testDoSomeLegacyStuff() { 
    MyLegacyClass.doSomeLegacyStuff(..); 
    assertTrue(..); 
    } 

    static class MockStrategy implements Strategy{ 

    public SomeLegacyClass doSomeStuff(SomeOtherLegacyClass legacyObj) { 
     // mock behavior however you want, record state however 
     // you'd like for test asserts. Good frameworks like Mockito exist 
     // to help create mocks 
    } 
    } 
} 
2

Refactorisez le code pour utiliser l'injection de dépendance. Utilisez ensuite votre framework DI préféré (Spring, Guice, ...) pour injecter vos ressources. Cela facilitera le basculement entre les objets ressources et les stratégies au moment de l'exécution.

Dans ce cas, vous pouvez injecter votre source de données.

EDIT: En fonction de votre nouvelle restriction, vous pouvez accomplir la même chose en utilisant un modèle de stratégie pour définir votre source de données lors de l'exécution. Vous pouvez probablement simplement utiliser un fichier de propriétés pour distinguer quelle stratégie créer et fournir la source de données. Cela ne nécessiterait pas de nouveau cadre, vous auriez juste à coder à la main la même fonctionnalité de base. Nous avons utilisé cette idée exacte avec un ServiceLocator pour fournir une source de données fictive lors de tests en dehors du conteneur Java EE.

1

Je pense que se lie ici la meilleure solution qui JNDI à une

locale

Le code existant utilise le jndiName comme ça:

DataSource datasource = (DataSource)initialContext.lookup(DATASOURCE_CONTEXT); 

Ainsi, la solution est lie local ici (ou tout ce que vous avez pour vous tester des données) dans un JNDI comme ça:

BasicDataSource dataSource = new BasicDataSource(); 
    dataSource.setDriverClassName(System.getProperty("driverClassName")); 
    dataSource.setUser("username"); 
    dataSource.setPassword("password"); 
    dataSource.setServerName("localhost"); 
    dataSource.setPort(3306); 
    dataSource.setDatabaseName("databasename"); 

Et puis la liaison:

Context context = new InitialContext(); 
context.bind("java:comp/env/jdbc/LegacyDataSource",datasource); 

Ou quelque chose de similaire, espérons que cela vous aide.

Bonne chance!