Рефакторинг статического метода / статического поля для тестирования

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

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

Обновлено: введение дополнительных сред выполнения не допускается.

Я обновил свой ответ с учетом вашего нового ограничения. На самом деле у нас есть система, которая должна была решать ту же проблему.

Robin 30.10.2008 16:41
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
8
1
4 714
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

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

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

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

Удачи!

Ответ принят как подходящий

Просто чтобы сделать предложение @ Robin о шаблоне стратегии более конкретным: (Обратите внимание, что общедоступный 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 test. Я не большой поклонник необходимости выполнять эту настройку / демонтаж, но это неприятный побочный эффект наличия API, основанного на статических методах (или синглтонах, если на то пошло). Что мне делать нравится в этом тесте, так это то, что он не использует 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
    }
  }
}

@ Scot Bale Просто спрашиваю, не может ли это привести к некоторому состоянию гонки и несоответствиям в многопоточной среде, например, если doSomeLegacyStuff привязан к сеансу пользователя

Emmanuel Ogoma 15.06.2020 13:28

Другие вопросы по теме