Перейти к основному содержимому

Интеграционное тестирование с Maven

· 8 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Обзор

Maven — самый популярный инструмент сборки в среде Java, а интеграционное тестирование — неотъемлемая часть процесса разработки. Поэтому вполне естественно настроить и выполнить интеграционные тесты с помощью Maven.

В этом руководстве мы рассмотрим несколько различных способов использования Maven для интеграционного тестирования и отделения интеграционных тестов от модульных тестов.

2. Подготовка

Чтобы приблизить демонстрационный код к реальному проекту, мы настроим приложение JAX-RS. Это приложение развертывается на сервере перед выполнением интеграционных тестов, а затем демонтируется.

2.1. Конфигурация Maven

Мы создадим наше REST-приложение на базе Jersey — эталонной реализации JAX-RS. Для этой реализации требуется пара зависимостей:

<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>2.27</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>2.27</version>
</dependency>

Мы можем найти последние версии этих зависимостей здесь и здесь .

Мы будем использовать плагин Jetty Maven для настройки среды тестирования. Этот подключаемый модуль запускает сервер Jetty на этапе тестирования до интеграции жизненного цикла сборки Maven, а затем останавливает его на этапе тестирования после интеграции .

Вот как мы настраиваем плагин Jetty Maven в pom.xml :

<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.11.v20180605</version>
<configuration>
<httpConnector>
<port>8999</port>
</httpConnector>
<stopKey>quit</stopKey>
<stopPort>9000</stopPort>
</configuration>
<executions>
<execution>
<id>start-jetty</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-jetty</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>

Когда сервер Jetty запустится, он будет прослушивать порт 8999 . Элементы конфигурации stopKey и stopPort используются исключительно целью остановки плагина, и их значение не важно с нашей точки зрения.

Здесь можно найти последнюю версию плагина Jetty Maven.

Еще одна вещь, на которую следует обратить внимание, это то, что мы должны установить для элемента упаковки в файле pom.xml значение war , иначе плагин Jetty не сможет запустить сервер:

<packaging>war</packaging>

2.2. Создание REST-приложения

Конечная точка приложения очень проста — возврат приветственного сообщения, когда запрос GET попадает в корень контекста:

@Path("/")
public class RestEndpoint {
@GET
public String hello() {
return "Welcome to ForEach!";
}
}

Вот как мы регистрируем класс конечной точки в Джерси:

package com.foreach.maven.it;

import org.glassfish.jersey.server.ResourceConfig;

public class EndpointConfig extends ResourceConfig {
public EndpointConfig() {
register(RestEndpoint.class);
}
}

Чтобы сервер Jetty знал о нашем приложении REST, мы можем использовать классический дескриптор развертывания web.xml :

<web-app 
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>rest-servlet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.foreach.maven.it.EndpointConfig</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>rest-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

Этот дескриптор должен быть помещен в каталог /src/main/webapp /WEB-INF , чтобы сервер его распознал.

2.3. Клиентский тестовый код

Все тестовые классы в следующих разделах содержат один метод:

@Test
public void whenSendingGet_thenMessageIsReturned() throws IOException {
String url = "http://localhost:8999";
URLConnection connection = new URL(url).openConnection();
try (InputStream response = connection.getInputStream();
Scanner scanner = new Scanner(response)) {
String responseBody = scanner.nextLine();
assertEquals("Welcome to ForEach!", responseBody);
}
}

Как мы видим, этот метод ничего не делает, кроме как отправляет запрос GET в веб-приложение, которое мы настроили ранее, и проверяет ответ.

3. Интеграционное тестирование в действии

В отношении интеграционного тестирования важно отметить, что методы тестирования часто выполняются довольно долго.

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

Удобным способом разделения интеграционных тестов является использование профилей сборки. Такая конфигурация позволяет нам выполнять интеграционные тесты только при необходимости — путем указания подходящего профиля.

В следующих разделах мы настроим все интеграционные тесты с помощью профилей сборки.

4. Тестирование с помощью отказоустойчивого плагина

Самый простой способ запустить интеграционные тесты — использовать отказоустойчивый плагин Maven `` .

По умолчанию подключаемый модуль Maven surefire выполняет модульные тесты на этапе тестирования , а подключаемый модуль отказоустойчивости выполняет интеграционные тесты на этапе интеграционного тестирования .

Мы можем назвать тестовые классы с разными шаблонами для этих плагинов, чтобы отдельно собирать вложенные тесты.

Соглашения об именах по умолчанию, применяемые surefire и failsafe , различаются, поэтому нам просто нужно следовать этим соглашениям, чтобы разделить модульные и интеграционные тесты.

Выполнение плагина surefire включает в себя все классы, имя которых начинается с Test или заканчивается на Test , Tests или TestCase . Напротив, отказоустойчивый подключаемый модуль выполняет тестовые методы в классах, имя которых начинается с IT или заканчивается на IT или ITCase .

Здесь мы можем найти документацию по включению тестов для surefire , а здесь — для failsafe .

Давайте добавим отказоустойчивый плагин в POM с конфигурацией по умолчанию:

<profile>
<id>failsafe</id>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.0</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

По этой ссылке можно найти последнюю версию отказоустойчивого плагина.

С приведенной выше конфигурацией на этапе интеграционного тестирования будет выполняться следующий метод тестирования :

public class RestIT {
// test method shown in subsection 2.3
}

Поскольку сервер Jetty запускается на этапе pre-integration-test и выключается на этапе post-integration-test , тест, который мы только что видели, проходит с помощью этой команды:

mvn verify -Pfailsafe

Мы также можем настроить шаблоны именования, чтобы включить классы с разными именами:

<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<includes>
<include>**/*RestIT</include>
<include>**/RestITCase</include>
</includes>
</configuration>
...
</plugin>

5. Тестирование с помощью плагина Surefire

Помимо отказоустойчивого плагина, мы также можем использовать плагин surefire для выполнения модульных и интеграционных тестов на разных этапах.

Предположим, мы хотим назвать все интеграционные тесты суффиксом IntegrationTest . Так как плагин surefire по умолчанию запускает тесты с таким названием в тестовой фазе, нам нужно исключить их из выполнения по умолчанию:

<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludes>
<exclude>**/*IntegrationTest</exclude>
</excludes>
</configuration>
</plugin>

Последняя версия этого плагина здесь .

Мы убрали все тестовые классы, имена которых заканчиваются на IntegrationTest , из жизненного цикла сборки. Пришло время поставить их обратно с профилем:

<profile>
<id>surefire</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<excludes>
<exclude>none</exclude>
</excludes>
<includes>
<include>**/*IntegrationTest</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

Вместо того, чтобы привязывать цель тестирования плагина surefire к фазе тестовой сборки, как обычно, мы привязали ее к фазе интеграционного тестирования . Затем плагин запустится во время процесса интеграционного тестирования.

Обратите внимание, что мы должны установить для элемента exclude значение none , чтобы переопределить исключение, указанное в базовой конфигурации.

Теперь давайте определим класс интеграционного теста с нашим шаблоном именования:

public class RestIntegrationTest {
// test method shown in subsection 2.3
}

Этот тест будет запущен командой:

mvn verify -Psurefire

6. Тестирование с плагином Cargo

Мы можем использовать плагин surefire с грузовым плагином Maven . Этот плагин поставляется со встроенной поддержкой встроенных серверов, которые очень полезны для интеграционного тестирования.

Подробнее об этой комбинации можно узнать здесь .

7. Тестирование с помощью JUnit @Category

Удобным способом выборочного выполнения тестов является использование аннотации @ Category в среде JUnit 4. Эта аннотация позволяет исключить определенные тесты из модульного тестирования и включить их в интеграционное тестирование.

Во-первых, нам нужен интерфейс или класс для работы в качестве идентификатора категории:

package com.foreach.maven.it;

public interface Integration { }

Затем мы можем украсить тестовый класс аннотацией @Category и идентификатором интеграции :

@Category(Integration.class)
public class RestJUnitTest {
// test method shown in subsection 2.3
}

Вместо того, чтобы объявлять аннотацию @Category в тестовом классе, мы также можем использовать ее на уровне метода для категоризации отдельных тестовых методов.

Исключить категорию из этапа тестовой сборки очень просто:

<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludedGroups>com.foreach.maven.it.Integration</excludedGroups>
</configuration>
</plugin>

Включить категорию « Интеграция» в фазу интеграционного тестирования также просто:

<profile>
<id>category</id>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<includes>
<include>**/*</include>
</includes>
<groups>com.foreach.maven.it.Integration</groups>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

Теперь мы можем запускать интеграционные тесты с помощью команды Maven:

mvn verify -Pcategory

8. Добавление отдельного каталога для интеграционных тестов

Иногда желательно иметь отдельный каталог для интеграционных тестов. Такая организация тестов позволяет нам полностью изолировать интеграционные тесты от модульных тестов.

Для этой цели мы можем использовать вспомогательный плагин сборки Maven:

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-integration-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/integration-test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>

Здесь мы можем найти последнюю версию этого плагина.

Конфигурация, которую мы только что видели, добавляет тестовый исходный каталог в сборку. Давайте добавим определение класса в этот новый каталог:

public class RestITCase {
// test method shown in subsection 2.3
}

Пришло время запустить интеграционные тесты в этом классе:

mvn verify -Pfailsafe

Подключаемый модуль отказоустойчивости Maven будет выполнять методы в этом тестовом классе из-за конфигурации, которую мы установили в подразделе 3.1.

Исходный каталог теста часто идет с каталогом ресурсов. Мы можем добавить такой каталог в другой элемент исполнения в конфигурацию плагина:

<executions>
...
<execution>
<id>add-integration-test-resource</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/integration-test/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>

9. Заключение

В этой статье мы рассмотрели использование Maven для запуска интеграционных тестов с сервером Jetty, уделив особое внимание настройке подключаемых модулей Maven surefire и failsafe .

Полный исходный код этого руководства можно найти на GitHub .