Статьи

Тестирование Java EE 6, часть II — Введение в Arquillian и ShrinkWrap

В первой части тестирования Java EE 6 я кратко представил встраиваемый API-интерфейс EJB 3.1, использующий встроенный контейнер Glassfish, чтобы продемонстрировать, как запустить контейнер, найти bean-компонент в пути к классам проекта и выполнить очень простой интеграционный тест.

Этот пост посвящен Arquillian и ShrinkWrap, а также тому, почему они являются отличными инструментами для интеграционного тестирования корпоративных Java-приложений.

Исходный код, использованный для этого поста, доступен на GitHub в папке arquillian-shrinkwrap .

Инструменты

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

  • Управляет жизненным циклом одного или нескольких контейнеров
  • Связывает тестовый набор, зависимые классы и ресурсы в архивы ShrinkWrap.
  • Развертывает архивы в контейнеры
  • Обогащает контрольный пример внедрением зависимостей и другими декларативными сервисами
  • Выполняет тесты внутри (или против) контейнеров
  • Возвращает результаты тесту для отчета

Упаковочная

ShrinkWrap, центральный компонент Arquillian, предоставляет простой механизм для сборки архивов, таких как JAR, WAR и EAR, с дружественным и свободным API.

Одним из основных преимуществ использования Arquillian является то, что вы запускаете тесты в удаленном контейнере (то есть на сервере приложений). Это означает, что вы будете тестировать реальную сделку . Никаких издевательств. Даже не встроенные среды выполнения!

Повестка дня

Следующие темы будут освещены в этом посте:

  • Настройте инфраструктуру Arquillian в Java-проекте на основе Maven
  • Внедрить EJB и управляемые компоненты (CDI) непосредственно в тестовые экземпляры
  • Уровень тестирования Java Persistence API (JPA)
  • Запустите Arquillian в режиме клиента
  • Запуск и отладка тестов Arquillian внутри вашей IDE

Настройте Maven для запуска интеграционных тестов

Для запуска интеграционных тестов с Maven нам нужен другой подход. Под другим подходом я имею в виду другой плагин: Maven Failsafe Plugin .

Плагин Failsafe является форком плагина Maven Surefire, предназначенного для запуска интеграционных тестов.

Цели плагина Failsafe предназначены для запуска после фазы пакета, на этапе тестирования интеграции.

Жизненный цикл Maven имеет четыре фазы для запуска интеграционных тестов:

  • тест перед интеграцией: на этом этапе мы можем запустить любой требуемый сервис или выполнить любое действие, например запустить базу данных или запустить веб-сервер, что угодно …
  • интеграция-тест: отказоустойчивый тест запустится на этом этапе, поэтому после запуска всех необходимых сервисов.
  • пост-интеграционный тест: время для закрытия всех сервисов…
  • verify: failsafe выполняет другую цель, которая интерпретирует результаты тестов здесь, если какие-либо тесты не прошли, failsafe отобразит результаты и выйдет из сборки.

Настройка Failsafe в POM:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- clip -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <skipTests>true</skipTests>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <encoding>UTF-8</encoding>
    </configuration>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
        <execution>
            <id>verify</id>
            <goals>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<!-- clip -->

По умолчанию плагин Surefire **/*TestCase.java тестовые классы **/Test*.java , **/*Test.java и **/*TestCase.java . Плагин Failsafe будет искать **/IT*.java , **/*IT.java и **/*ITCase.java . Если вы используете оба плагина Surefire и Failsafe, убедитесь, что вы используете это соглашение об именах, чтобы упростить определение того, какие тесты выполняются каким плагином.

Настроить инфраструктуру Arquillian в Maven

Сконфигурируйте ваш дескриптор проекта Maven для использования Arquillian, добавив следующий фрагмент XML:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!-- clip -->
<repositories>
    <repository>
        <id>jboss-public-repository-group</id>
        <name>JBoss Public Repository Group</name>
    </repository>
</repositories>
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.arquillian</groupId>
            <artifactId>arquillian-bom</artifactId>
            <version>1.0.1.Final</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <dependency>
        <groupId>org.jboss.arquillian.testng</groupId>
        <artifactId>arquillian-testng-container</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.4</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.jboss.spec</groupId>
        <artifactId>jboss-javaee-6.0</artifactId>
        <version>3.0.1.Final</version>
        <scope>provided</scope>
        <type>pom</type>
    </dependency>
</dependencies>
 
<profiles>
    <profile>
        <id>jbossas-remote-7</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <dependencies>
            <dependency>
                <groupId>org.jboss.as</groupId>
                <artifactId>jboss-as-arquillian-container-remote</artifactId>
                <version>7.1.1.Final</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </profile>
</profiles>
<!-- clip -->

У Arquillian есть огромный список контейнерных адаптеров . Тест Arquillian может быть выполнен в любом контейнере, который совместим с моделью программирования, используемой в тесте. Однако в этом посте используется только JBoss AS 7.
Как и в первой части тестирования Java EE 6 , я решил использовать среду тестирования TestNG , но, опять же, JUnit должен работать так же хорошо.

Создать тестируемые компоненты

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Stateless
public class UserServiceBean {
 
    @PersistenceContext
    private EntityManager em;
 
    public User addUser(User user) {
        em.persist(user);
        return user;
    }
 
    // Annotation says that we do not need to open a transaction
    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    public User findUserById(Long id) {
        return em.find(User.class, id);
    }
}

В приведенном выше коде я использую JPA, и поэтому нам нужен постоянный модуль.
Модуль персистентности определяет набор всех классов сущностей, которые управляются экземплярами EntityManager в приложении. Этот набор классов сущностей представляет данные, содержащиеся в одном хранилище данных.
Единицы постоянства определяются файлом конфигурации persistence.xml :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
 
 
    version="2.0">
    <persistence-unit name="example">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

В этом примере я использую пример источника данных, который использует базу данных H2 и уже настроен с JBoss AS 7.

Наконец, нам также нужен объект, который отображается на таблицу в базе данных:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Entity
public class User {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NotNull
    private String name;
 
    // Removed constructors, getters and setters for brevity
 
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + "]";
    }
}

Тест JPA с Аркиллианом

Теперь мы готовы написать наш первый тест по Арквилиану.
Тестовый пример Arquillian выглядит как модульный тест с некоторыми дополнениями. Должно быть три вещи:

  • Расширьте класс Arquillian (это специфично для TestNG, с JUnit вам нужна аннотация @RunWith(Arquillian.class) для класса)
  • Открытый статический метод с аннотацией @Deployment, который возвращает архив ShrinkWrap
  • По крайней мере один метод аннотирован @Test
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UserServiceBeanIT extends Arquillian {
 
    private static final Logger LOGGER = Logger.getLogger(UserServiceBeanIT.class.getName());
 
    @Inject
    private UserServiceBean service;
 
    @Deployment
    public static JavaArchive createTestableDeployment() {
        final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "example.jar")
                .addClasses(User.class, UserServiceBean.class)
                .addAsManifestResource("META-INF/persistence.xml", "persistence.xml")
                // Enable CDI
                .addAsManifestResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
 
        LOGGER.info(jar.toString(Formatters.VERBOSE));
 
        return jar;
    }
 
    @Test
    public void callServiceToAddNewUserToDB() {
        final User user = new User("Ike");
        service.addUser(user);
        assertNotNull(user.getId(), "User id should not be null!");
    }
}

Этот тест прост, он вставляет нового пользователя и проверяет, что свойство id было заполнено сгенерированным значением из базы данных.
Поскольку тест обогащен Arquillian, вы можете вводить EJB и управляемые bean-компоненты, как правило, используя аннотации @EJB или @Inject .
Метод, аннотированный @Deployment использует ShrinkWrap для создания архива JAR, который будет развернут в контейнере и для которого будут выполняться ваши тесты. ShrinkWrap изолирует классы и ресурсы, которые нужны тесту, от остальной части пути к классам, вы должны включить каждый компонент, необходимый для запуска теста, в архив развертывания.

Режим клиента

Arquillian поддерживает три режима пробного запуска:

  • Режим In-Container предназначен для тестирования внутренних компонентов вашего приложения. Это дает Arquillian возможность общаться с тестом, обогащать тест и запускать тест дистанционно. В этом режиме тест выполняется в удаленном контейнере; Arquillian использует этот режим по умолчанию.
  • Клиентский режим предназначен для проверки того, как ваше приложение используется клиентами. В отличие от режима в контейнере, который переупаковывает и переопределяет выполнение теста, режим клиента делает как можно меньше. Он не перепаковывает ваше @Deployment и не перенаправляет выполнение теста на удаленный сервер. Ваш тестовый пример работает в вашей JVM, как и ожидалось, и вы можете тестировать контейнер снаружи, как его видят ваши клиенты. Единственное, что делает Arquillian — это контролирует жизненный цикл вашего @Deployment .
  • Смешанный режим позволяет смешивать два режима работы в одном и том же тестовом классе.

Для запуска Arquillian в режиме клиента давайте сначала соберем тестируемый сервлет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@WebServlet("/User")
public class UserServlet extends HttpServlet {
 
    private static final long serialVersionUID = -7125652220750352874L;
 
    @Inject
    private UserServiceBean service;
 
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/plain");
 
        PrintWriter out = response.getWriter();
        out.println(service.addUser(new User("Ike")).toString());
        out.close();
    }
 
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        doGet(request, response);
    }
}

А теперь давайте проверим это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class UserServletIT extends Arquillian {
 
    private static final Logger LOGGER = Logger.getLogger(UserServletIT.class.getName());
 
    // Not managed, should be used for external calls (e.g. HTTP)
    @Deployment(testable = false)
    public static WebArchive createNotTestableDeployment() {
        final WebArchive war = ShrinkWrap.create(WebArchive.class, "example.war")
                .addClasses(User.class, UserServiceBean.class, UserServlet.class)
                .addAsResource("META-INF/persistence.xml")
                // Enable CDI
                .addAsWebInfResource(EmptyAsset.INSTANCE, ArchivePaths.create("beans.xml"));
 
        LOGGER.info(war.toString(Formatters.VERBOSE));
 
        return war;
    }
 
    @RunAsClient // Same as @Deployment(testable = false), should only be used in mixed mode
    @Test(dataProvider = Arquillian.ARQUILLIAN_DATA_PROVIDER)
    public void callServletToAddNewUserToDB(@ArquillianResource URL baseURL) throws IOException {
        // Servlet is listening at <context_path>/User
        final URL url = new URL(baseURL, "User");
        final User user = new User(1L, "Ike");
 
        StringBuilder builder = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
        String line;
 
        while ((line = reader.readLine()) != null) {
            builder.append(line);
        }
        reader.close();
 
        assertEquals(builder.toString(), user.toString());
    }
}

Хотя этот тест очень прост, он позволяет вам протестировать несколько уровней вашего приложения с помощью одного вызова метода.

Запустите тесты внутри Eclipse

Вы можете запустить тест Arquillian изнутри вашей IDE, как модульный тест.

Запустите тест Arquillian

(Нажмите на картинку для увеличения)

  • Установите плагины TestNG и JBoss Tools Eclipse.
  • Добавьте новый сервер JBoss AS в Eclipse:
  • Запустите сервер JBoss AS:
  • Запустите тестовый пример из Eclipse, щелкните правой кнопкой мыши на тестовом файле в Project Explorer и выберите

Run As > TestNG Test :

Результат должен выглядеть примерно так:

Отладка теста Arquillian

(Нажмите на картинку для увеличения)

Поскольку мы используем удаленный контейнер, Debug As > TestNG Test не приводит к активации точек останова.
Вместо этого нам нужно запустить контейнер в режиме отладки и подключить отладчик. Это потому, что тест выполняется в другой виртуальной машине Java, нежели в исходном тестовом средстве.
Единственное изменение, которое необходимо внести для отладки теста, — запустить сервер JBoss AS в режиме отладки:

  • Запустите режим отладки сервера JBoss AS:
  • Добавьте нужные вам точки останова в свой код.
  • И отладить его, щелкнув правой кнопкой мыши на тестовом файле в Project Explorer и выбрав

Run As > TestNG Test :

    Больше ресурсов

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

    Похожие сообщения

    Ссылка: тестирование Java EE 6, часть II — введение в Arquillian и ShrinkWrap от нашего партнера по JCG Сэмюэля Сантоса в блоге Samaxes .