Дилемма тестирования устройства: использование источника данных JNDI без запуска JBoss или Spring
Заявление о проблемах
Я хочу иметь возможность запускать тесты junit для методов, которые подключаются к базе данных.
Текущая настройка
Eclipse Java EE IDE - Java-код не использует фреймворк. Разработчики (включая меня) хотят более надежного тестирования текущего унаследованного кода, прежде чем пытаться переместить код в структуру Spring, чтобы мы могли доказать, что поведение по-прежнему правильное.
JBoss 4.2 - ограничение версии по программному обеспечению поставщика (Adobe LiveCycle ES2); В нашем веб-приложении Java используется эта настройка JBoss для запуска и использования API Adobe LiveCycle.
Мы не смогли успешно запустить настроенный JBoss поставщиком в Eclipse - мы потратили недели на это, включая контакт с компанией, которая обеспечивает нашу поддержку конфигурации JBoss для Adobe LiveCycle. Предположительно проблема связана с проблемой ограничения памяти с настройками в Eclipse, но изменение настроек памяти до сих пор не удалось в успешном запуске сервера JBoss в Eclipse. На данный момент попытка запустить JBoss внутри Eclipse приостановлена.
Соединение с базой данных определяется в источнике данных JNDI, который загружается JBoss при запуске. Как нашему веб-приложению, так и Adobe LiveCycle необходимо создать подключения к этому источнику данных.
код
Я замалчиваю проверку ошибок и структуру классов в этом фрагменте кода, чтобы сосредоточиться на сути вопроса. Надеюсь, это не вызовет проблем для других. Текст в квадратных скобках не является фактическим текстом.
Наш код для создания соединения выглядит примерно так:
Properties props = new Properties();
FileInputStream in = null;
in = new FileInputStream(System.getProperty("[Properties File Alias]"));
props.load(in);
String dsName = props.getProperty("[JNDI data source name here]");
InitialContext jndiCntx = new InitialContext();
DataSource ds = (DataSource) jndiCntx.lookup(dsName);
Ds.getConnection();
Я хочу иметь возможность тестировать методы, зависящие от этого кода, без каких-либо изменений.
Ссылка на псевдоним файла свойств в файле properties-service.xml:
<!-- ==================================================================== -->
<!-- System Properties Service -->
<!-- ==================================================================== -->
<!-- Allows rich access to system properties.-->
<mbean code="org.jboss.varia.property.SystemPropertiesService"
name="jboss:type=Service,name=SystemProperties">
<attribute name="Properties">
[Folder Alias]=[filepath1]
[Properties File Alias]=[filepath2]
</attribute>
</mbean>
Снимок из файла свойств, расположенного в файлеpath2
[JNDI data source name]=java:/[JNDI data source name]
XML файл JNDI для этого источника данных настроен следующим образом:
<datasources>
<local-tx-datasource>
<jndi-name>[JNDI data source name here]</jndi-name>
<connection-url>jdbc:teradata://[url]/database=[database name]</connection-url>
<driver-class>com.teradata.jdbc.TeraDriver</driver-class>
<user-name>[user name]</user-name>
<password>[password]</password>
<!-- sql to call on an existing pooled connection when it is obtained from pool -->
<check-valid-connection-sql>SELECT 1+1</check-valid-connection-sql>
</local-tx-datasource>
</datasources>
Мои мысли о том, где решение может быть
Есть ли что-то, что я могу сделать в методе @BeforeClass, чтобы сделать свойства, которые код выше доступен, без JBoss? Может быть, каким-то образом использовать метод setProperty класса java.util.Properties? Я также хотел бы использовать тот же JNDI файл XML, который JBoss читает, если это возможно, чтобы уменьшить повторяющиеся параметры конфигурации.
До сих пор все мои исследования заканчиваются рекомендацией "Использовать Spring", но я не думаю, что был готов открыть эту банку червей. Я не эксперт в JBoss, но если для получения более полезного ответа необходимы более подробные сведения о нашей настройке JBoss, я сделаю все возможное, чтобы их получить, хотя мне, вероятно, понадобятся некоторые указатели на то, где искать.
Ссылки на ресурсы Stackoverflow:
Поиск Jndi в junit с помощью spring
Исходный источник данных JNDI контейнера
Другие ссылки на исследования:
http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Properties.html
http://docs.oracle.com/javase/jndi/tutorial/basics/prepare/initial.html
Ответы
Ответ 1
Там очень простой ответ на вашу проблему, но вам это не понравится: Не делать.
По определению a unit test должен проверять функциональность одного устройства (размер которого может меняться, но он должен быть самодостаточным). Создание установки, в которой тест зависит от веб-служб, баз данных и т.д., Является контрпродуктивным: он замедляет ваши тесты, он включает в себя gzillion возможных вещей, которые могут пойти не так (неудачные сетевые подключения, изменения в наборах данных,... ) во время теста, которые не имеют никакого отношения к фактическому коду, над которым вы работаете, и что самое главное: он делает тестирование намного сложнее и сложнее.
Вместо этого вы должны искать способы отделить устаревший код от любых источников данных, чтобы вы могли легко заменить mock объекты или аналогичные тест удваивается во время тестирования.
Вы должны создать тесты для проверки целостности всего вашего стека, но они называются интеграционными тестами, и они работают на более высоком уровне абстракции. Мне лично нравится откладывать их написание до тех пор, пока сами подразделения не будут установлены, протестированы и не будут работать - по крайней мере, до тех пор, пока вы не достигнете точки, когда вы больше не ожидаете изменений в звонках и протоколах службы на ежедневной основе.
В вашем случае наиболее очевидной стратегией было бы инкапсулировать все вызовы веб-службы в один или несколько отдельных классов, извлечь интерфейс, на который могут влиять бизнес-объекты, и использовать макеты, реализующие этот же интерфейс для модульного тестирования.
Например, если у вас есть бизнес-объект, который вызывает адресную базу данных, вам необходимо скопировать код поиска JNDI в новый класс службы AddressServiceImpl
. Его общедоступные методы должны имитировать все сигнатуры методов вашего источника данных JNDI. Затем вы извлекаете в интерфейс AddressService
.
Затем вы можете написать простой интеграционный тест, чтобы убедиться, что новый класс работает: вызовите все методы один раз и посмотрите, получите ли вы правильные результаты. Красота заключается в том, что вы можете предоставить конфигурацию JNDI, которая указывает на тестовую базу данных (вместо исходной), которую вы можете заполнить тестовыми наборами данных, чтобы всегда получать ожидаемые результаты. Для этого вам необязательно нужен экземпляр JBoss (хотя у меня никогда не было проблем с интеграцией eclipse) - любой другой поставщик JNDI должен работать, если сам источник данных ведет себя одинаково. И чтобы быть ясным: вы проверите это один раз, а затем забудьте об этом. По крайней мере, до тех пор, пока фактические методы обслуживания не изменятся.
После проверки работоспособности службы следующая задача состоит в том, чтобы пройти через все зависимые классы и заменить прямые вызовы на источник данных вызовами интерфейса AddressService. И с этого момента у вас есть надлежащая настройка для реализации модульных тестов для реальных бизнес-методов, без необходимости беспокоиться о вещах, которые должны быть протестированы в другом месте;)
ИЗМЕНИТЬ
Вторые рекомендации для Mockito. Действительно хорошо!
Ответ 2
У меня была очень похожая ситуация с некоторым устаревшим кодом в JBoss AS7, для которого рефакторинг был бы выход за рамки.
Я отказался от попыток получить источник данных из JBoss, потому что он не поддерживает удаленный доступ к источникам данных, которые я подтвердил при попытке.
В идеале, однако, вы не хотите, чтобы ваши модульные тесты зависели от исполняемого экземпляра JBoss для запуска, и вы действительно не хотите, чтобы они запускались внутри JBoss. Это будет противоречить концепции автономных модульных тестов (даже если вам все равно нужна работающая база данных:)).
К счастью, исходный контекст, используемый вашим приложением, не должен исходить из запущенного экземпляра JBoss. Посмотрев эту статью, на которую ссылался ответ на другой вопрос, я был способный создать свой собственный исходный контекст, заполнить его моим собственным источником данных.
Это работает без создания зависимостей в коде, потому что тестируемые классы обычно запускаются внутри контейнера, где они просто делают что-то подобное, чтобы получить контекст, предоставленный контейнером:
InitialContext ic = new InitialContext();
DataSource ds = (DataSource)ic.lookup(DATA_SOURCE_NAME);
Им не нужно указывать какую-либо среду для конструктора, поскольку она уже настроена контейнером.
Для того, чтобы ваши модульные тесты стояли в контейнере и предоставляли контекст, вы создаете его и связываете имя:
InitialContext ic = new InitialContext();
// Construct DataSource
OracleConnectionPoolDataSource ds = new OracleConnectionPoolDataSource();
ds.setURL("url");
ds.setUser("username");
ds.setPassword("password");
ic.bind(DATA_SOURCE_NAME, ds);
Это должно произойти в каждом методе тестового класса @BeforeClass
.
Теперь тестируемые классы получают мой начальный контекст при работе в модульных тестах и при развертывании контейнера.
Ответ 3
Если вы используете такие инструменты, как Git и Maven, с ними можно легко справиться. Зайдите в отдельный файл свойств UnitTest вдоль боковой разработки и qa. Используйте Maven и его средства profile
, чтобы указать профиль, который копирует ваш файл UnitTest, туда, куда он должен идти, так же, как и ваши dev и qa при запуске с активными различными профилями.
В этом нет магии; Spring вводит сложность больше всего. он определенно не вводит такую простоту.
Ответ 4
Вы можете запустить свои тесты с помощью поддельной реализации InitialContext
, которая возвращает все, что вам нужно, от вызовов lookup(String)
.
Инструмент для издевательств/фальсификации, который допускает такие поддельные реализации, JMockit. Поддельная реализация будет написана следующим образом:
public class FakeInitialContext extends MockUp<InitialContext>
{
@Mock
public Object lookup(String name)
{
// Return whatever is needed based on "name".
}
}
Чтобы применить его к тестовому прогону JUnit/TestNG, добавьте jmockit.jar в путь класса runtime (до junit.jar, если это так) и установите системное свойство "jmockit-mocks" в имя поддельного класса: -Djmockit-mocks=com.whatever.FakeInitialContext
.
Конечно, вы также можете написать настоящие модульные тесты JUnit/TestNG, где любая зависимость может быть легко издевается, используя API-интерфейс "Ожидания и проверки".
(PS: Для полного раскрытия я являюсь создателем проекта JMockit.)