Статьи

Модульная конфигурация Spring для интеграционного тестирования

Объектно-ориентированное программирование выступает за модульность для создания небольших и многократно используемых компонентов. Однако для этого есть и другие причины. В случае среды Spring модульность обеспечивает интеграционное тестирование, возможность тестирования системы или ее частей, включая конфигурацию сборки.

Почему так важно протестировать собранную систему с окончательной конфигурацией? Давайте рассмотрим простой пример — изготовление автомобиля. Модульное тестирование автомобиля было бы сродни тестированию всех болтов и болтов в отдельности, в то время как интеграционное тестирование автомобиля было бы похоже на вождение по кругу. При тестировании только компонентов автомобиля по отдельности продажа собранного автомобиля представляет собой огромный риск, поскольку ничто не гарантирует, что он будет вести себя правильно в реальных условиях.

Теперь, когда мы заявили, что интеграционное тестирование необходимо, чтобы гарантировать адекватный уровень внутреннего качества, пришло время включить интеграционное тестирование со средой Spring. Интеграционное тестирование основано на понятии SUT . Определение SUT — это определение границ между тем, что тестируется, и его зависимостями. Практически во всех случаях для настройки теста потребуется предоставить какой-то двойной тест для каждой требуемой зависимости. Конфигурирование этих тестовых двойников может быть достигнуто только путем модульной конфигурации Spring, чтобы они могли заменять зависимые компоненты, расположенные вне SUT.

Пример диаграммы зависимостей bean-компонента

Рис. 1 — Пример диаграммы зависимостей бина

Конфигурация Spring DI поставляется в 3 различных вариантах: XML — устаревший способ, автоматическое подключение и новейший JavaConfig. Мы посмотрим, как можно добиться модульности для каждого аромата. Смешанная модуляция DI может быть выведена из каждой отдельной записи.

автоматическое связывание

Автопроводка — это простой способ сборки приложений Spring. Это достигается за счет использования либо @Autowiringили @Inject. Давайте быстро рассмотрим автоматическую разводку: поскольку внедрение неявно, нет простого способа модульной конфигурации. Приложения, использующие автоматическую разводку, просто должны перейти на другой вариант DI, чтобы разрешить интеграционное тестирование.

XML

XML является устаревшим способом внедрения зависимостей, но все еще используется. Рассмотрим следующий монолитный XML-файл конфигурации:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:jee="http://www.springframework.org/schema/jee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">
  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource" />
  <bean id="productRepository" class="ProductRepository">
    <constructor-arg ref="dataSource" />
  </bean>
  <bean id="customerRepository" class="CustomerRepository">
    <constructor-arg ref="dataSource" />
  </bean>
  <bean id="orderRepository" class="OrderRepository">
    <constructor-arg ref="dataSource" />
  </bean>
  <bean id="orderService" class="OrderService">
    <constructor-arg ref="productRepository" index="0" />
    <constructor-arg ref="customerRepository" index="1" />
    <constructor-arg ref="orderRepository" index="2" />
  </bean>
</beans>

На этом этапе интеграционное тестирование orderServiceне так просто, как должно быть. В частности, нам необходимо:

  • Скачать сервер приложений
  • Настройте сервер для jdbc/MyDataSourceисточника данных
  • Развернуть все классы на сервере
  • Запустить сервер
  • Остановить сервер После теста (ов)

Конечно, все предыдущие задачи должны быть автоматизированы! Хотя это не невозможно благодаря таким инструментам, как Arquillian, это противоречит принципу KISS. Чтобы преодолеть эту проблему и сделать нашу жизнь (а также тестовое обслуживание) проще в процессе, требуются инструменты и дизайн. В части инструментов мы будем использовать локальную базу данных. Обычно такая база данных имеет вид в памяти, например, H2. В части проектирования он требует разделения наших компонентов путем создания двух разных фрагментов конфигурации, один из которых предназначен исключительно для подделки источника данных, а другой — для компонентов, составляющих SUT.

Затем мы будем использовать трюк пути к классу Maven: Maven помещает тестовый путь к классу перед основным путем при выполнении тестов. Таким образом, файлы, найденные в тестовом пути к классам, «переопределят» файлы с одинаковыми именами в основном пути к классам. Давайте создадим два фрагмента конфигурационных файлов:


«Реальный» источник данных JNDI как в монолитной конфигурации
<beans ...>
  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource" />
</beans>

Поддельный источник данных
<beans...>
  <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource">
      <property name="driverClassName" value="org.h2.Driver" />
      <property name="url" value="jdbc:h2:~/test" />
      <property name="username" value="sa" />
      <property name="maxActive" value="1" />
  </bean>
</beans>
  • Обратите внимание, что мы используем объект источника данных Tomcat, для этого требуется org.apache.tomcat:tomcat-jdbc:jarбиблиотека в тестовом пути к классам. Также обратите внимание на maxActiveсобственность. Это отражает максимальное количество подключений к базе данных. Рекомендуется всегда устанавливать его в 1 для тестовых сценариев, чтобы ошибки исчерпания пула соединений можно было проверять как можно раньше.

Окончательный макет выглядит следующим образом:

Рис. 2 — Структура проекта для конфигурации Spring XML Интеграционное тестирование

  1. JNDI источник данных
  2. Другие бобы
  3. Поддельный источник данных

Конечный main-config.xmlфайл выглядит так:

<?xml version="1.0" encoding="UTF-8"?>
<beans...>
  <import resource="classpath:datasource-config.xml" />
  <!-- other beans go here -->
</beans>

Такая структура является основой для включения интеграционного тестирования.

JavaConfig

JavaConfig — это самый последний способ настройки приложений Spring, обеспечивающий безопасность как во время компиляции (как автоматическое подключение), так и явное конфигурирование (как XML).

Вышеуказанные фрагменты источников данных могут быть «переведены» на Java следующим образом:

  • «Реальный» источник данных JNDI как в монолитной конфигурации
@Configuration
public class DataSourceConfig {
 
    @Bean
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("jdbc/MyDataSource");
    }
}

Поддельный источник данных

public class FakeDataSourceConfig {
 
    public DataSource dataSource() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:~/test");
        dataSource.setUsername("sa");
        dataSource.setMaxActive(1);
        return dataSource;
    }
}

Однако при использовании JavaConfig возникают две проблемы.

  1. Невозможно использовать тот же трюк пути к классам при импорте, что и ранее с XML, так как Java запрещает иметь 2 (или более) классов с одинаковым квалифицированным именем, загружаемых одним и тем же загрузчиком классов (как в случае с Maven). Следовательно, фрагменты конфигурации JavaConfig не должны явно импортировать другие фрагменты, но должны оставлять ответственность за сборку фрагмента своим пользователям (приложению или тестам), чтобы имена могли отличаться, например :
@ContextConfiguration(classes = {MainConfig.class, FakeDataSource.class})
public class SimpleDataSourceIntegrationTest extends AbstractTestNGSpringContextTests {
 
    @Test
    public void should_check_something_useful() {
        // Test goes there
    }
}
  • Основной фрагмент конфигурации использует компонент источника данных из другого фрагмента конфигурации. Это требует, чтобы первый имел ссылку на последний. Это получается с помощью @Autowiredаннотации (один из немногих соответствующих его использования).
  • @Configuration
    public class MainConfig {
     
        @Autowired
        private DataSource dataSource;
     
        // Other beans go there. They can use dataSource!
    }

    Резюме

    В этой статье я показал, как можно выполнить интеграционное тестирование с поддельным источником данных, модулируя монолитную конфигурацию Spring в различные фрагменты конфигурации, будь то XML или JavaConfig.

    Однако сфера тестирования интеграции — с Spring или без, огромна. Если вы хотите пойти дальше, я буду держать разговор по интеграции тестирования в Agile Тур Лондон на октябрь 24 — го и на Java Дни Киева на 17 октября й -18 — й .

    Эта статья является сокращенной версией части весенней главы « Интеграционное тестирование из окопов» . Взгляните на это, есть даже бесплатный образец главы!