2008-10-30 3 views
8

У меня есть следующий код: унаследованногоРефакторинг статического метода/статическое поле для тестирования

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

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

Этот класс работает в J2EE-контейнере.

Теперь я хотел бы проверить класс за пределами контейнера.

Какая стратегия является лучшей? Рефакторинг в основном разрешен.

Доступ к источнику LegacyDataSource разрешен (тест не обязательно должен быть «чистым» модульным тестом).

РЕДАКТ. Представление дополнительных фреймворков времени выполнения не допускается.

+0

Я обновил свой ответ на основе вашего нового ограничения. На самом деле у нас есть система, которая должна была решить ту же проблему. – Robin 2008-10-30 13:41:23

ответ

7

Просто, чтобы сделать @ предложение Робина шаблона стратегии более конкретно: (Обратите внимание на то, что общественность API вашего первоначального вопроса остается неизменным.)

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 
    } 
} 

... и тест JUnit. Я не большой поклонник того, чтобы делать это обслуживание установки/слежения, но это неудачный побочный эффект от использования API на основе статических методов (или Singletons, если на то пошло). Что я делаю do Как и в этом тесте, он не использует JNDI - это хорошо, потому что (а) он будет работать быстро, и (b) единичный тест должен только тестировать бизнес-логику в методе doSomeLegacyStuff() в любом случае, а не тестирование фактического источника данных. (Кстати, это предполагает, что тестовый класс находится в той же упаковке, что и 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

Рефакторинг кода для использования инъекции зависимостей. Затем используйте предпочтительную структуру DI (Spring, Guice, ...), чтобы ввести ваши ресурсы. Это позволит легко переключаться между объектами ресурсов и стратегиями во время выполнения.

В этом случае вы можете ввести свой источник данных.

EDIT: на основе вашего нового ограничения вы можете выполнить одно и то же, используя шаблон стратегии, чтобы установить источник данных во время выполнения. Возможно, вы можете просто использовать файл свойств, чтобы отличить, какую стратегию создавать и поставлять источник данных. Для этого не потребуется никаких новых фреймворков, вы просто должны кодировать одну и ту же базовую функциональность. Мы использовали эту точную идею с ServiceLocator для предоставления ложного источника данных при тестировании вне контейнера Java EE.

1

Я думаю, что лучшим решением здесь привязать этот JNDI к местным

унаследованного кода использует jndiName так:

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

Итак, решение здесь связывают местный (или все, что у вас есть для вас проверить данные) в JNDI подобное:

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

И затем связывание:

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

Или что-то подобное, надеюсь, что это поможет вам.

Удачи вам!