Рефакторинг статического метода / статического поля для тестирования - PullRequest
8 голосов
/ 30 октября 2008

У меня есть следующий устаревший код:

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 разрешен (тест не должен быть «чистым» модульным тестом).

РЕДАКТИРОВАТЬ: Введение дополнительных сред выполнения не допускается.

Ответы [ 3 ]

7 голосов
/ 30 октября 2008

Просто чтобы сделать предложение Робина о шаблоне стратегии более конкретным: (Обратите внимание, что публичный 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 в этом отношении). Что мне делать нравится в этом тесте, так это то, что он не использует JNDI - это хорошо, потому что (а) он будет работать быстро, и (б) модульный тест должен проверять бизнес-логику только в 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 голосов
/ 30 октября 2008

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

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

РЕДАКТИРОВАТЬ: Исходя из вашего нового ограничения, вы можете сделать то же самое, используя шаблон стратегии для установки источника данных во время выполнения. Вероятно, вы можете просто использовать файл свойств, чтобы определить, какую стратегию создавать и предоставлять источник данных. Это не потребует никакой новой структуры, вы просто будете вручную кодировать ту же базовую функциональность. Мы использовали эту точную идею с ServiceLocator для предоставления фиктивного источника данных при тестировании вне контейнера Java EE.

1 голос
/ 30 октября 2008

Я думаю, что лучшим решением здесь является привязка 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); 

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

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...