ДжазТим — надежный технологический партнер

Agile разработка ПО на Java

Настройка и использование фреймворка Arquillian для работы c сервером WildFly

arquillian

Фреймворк Arquillian применяется для тестирования Java EE приложений. В частности, позволяет тестировать классы использующие CDI/ EJB зависимости. Предоставляет возможность писать как функциональные, так и интеграционные тесты. Тесты выполняются с использованием полнофункционального сервера приложений, это гарантирует, что поведение класса в тесте будет таким же, как и при его работе в приложении.

Есть возможность использовать для тестов как удаленный сервер, так и отдельный запускаемый в начале теста embedded-сервер.

Официальный сайт http://arquillian.org/

В данном мануале приводится способ настройки встроенного сервера приложений.

Подключение maven-зависимостей

Зависимости, которые нужно добавить в проект для корректной работы Arquillian:

<!--ARQUILLIAN-->
<dependency>
    <groupId>org.jboss.arquillian.core</groupId>
    <artifactId>arquillian-core-api</artifactId>
    <version>1.1.10.Final</version>
    <scope>test</scope>
</dependency>

Библиотека осуществляет сборку war, jar, ear.

<dependency>
<groupId>org.jboss.shrinkwrap</groupId>
    <artifactId>shrinkwrap-api</artifactId>
    <version>1.2.3</version>
    <scope>test</scope>
</dependency>

Этот пакет определяет контекст аннотации и интерфейсы для (CDI).

<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>1.0-SP1</version>
    <scope>test</scope>
</dependency>

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

<dependency>
    <groupId>org.jboss.arquillian.testng</groupId>
    <artifactId>arquillian-testng-container</artifactId>
    <version>1.1.10.Final</version>
    <scope>test</scope>
</dependency>

Зависимости для WildFly сервера, который будет использовать Arquillian:

<dependency>
    <groupId>org.wildfly.arquillian</groupId>
    <artifactId>wildfly-arquillian-container-embedded</artifactId>
    <version>1.0.2.Final</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.wildfly</groupId>
    <artifactId>wildfly-embedded</artifactId>
    <version>9.0.2.Final</version>
    <scope>test</scope>
</dependency>

Далее следует настроить maven-плагины, которые будут осуществлять сборку и настройку сервера приложения перед тестами.

Загрузка, распаковка сервера и драйверов БД:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.8</version>
    <executions>
        <execution>
            <id>unpack</id>
            <phase>process-test-classes</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
            <configuration>
                <!--скачивание и распаковка сервера в каталог 
target-->
                <artifactItems>
                    <artifactItem>
                        <groupId>org.wildfly</groupId>
                        <artifactId>wildfly-dist</artifactId>
                        <version>9.0.2.Final</version>
                        <type>zip</type>
                        <overWrite>false</overWrite>
                        <outputDirectory>target</outputDirectory>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
        <execution>
            <id>copy-db-driver</id>
            <phase>process-test-resources</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>
<!--копирование драйвера postgres в каталог сервера-->
                <artifactItems>
                    <artifactItem>
                        <groupId>org.postgresql</groupId>
                        <artifactId>postgresql</artifactId>
                        <version>9.4-1206-jdbc41</version>
                        <outputDirectory>${project.basedir}/target/
wildfly-9.0.2.Final/modules/org/postgres/main</outputDirectory>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
    </executions>
</plugin>

Для настройки конфигурации сервера WildFly необходимо подготовить тестовую версию файла standalone.xml. Пример файла. Стандартная конфигурация сервера находится в папке с распакованным сервером /standalone/configuration/standalone.xml. При сборке тестового сервера конфигурация должны быть скопирована в каталог конфигурации сервера.

Конфигурация драйверов БД

Необходимо скопировать на сервер конфигурацию драйверов, файл module.xml, в нем должен быть указано имя jar-архива, который maven выкачает и скопирует на сервер (path=«postgresql-9.4-1206-jdbc41.jar»). И имя модуля драйверов, которое будет использовано для их подключения в standalone.xml (name=«org.postgres»).

<?xml version="1.0" encoding="UTF-8"?>
<module  name="org.postgres">
    <resources>
        <resource-root path="postgresql-9.4-1206-jdbc41.jar"/>
    </resources>

    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

Копирование конфигурационных файлов в каталог сервера

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <id>copy-configs</id>
            <phase>process-test-classes</phase>
            <configuration>
                <tasks>
<!--файл с конфигурацией драйвера бд -->
                    <copy file="${project.basedir}/src/test/resources/
wildfly_config/module.xml" toFile="${project.basedir}/target/
wildfly-9.0.2.Final/modules/org/postgres/main/module.xml"></copy>
<!-- файл с основной конфигурацией сервера-->
                    <copy file="${project.basedir}/src/test/resources/
wildfly_config/standalone.xml" toFile="${project.basedir}/target/
wildfly-9.0.2.Final/standalone/configuration/standalone.xml"></copy>
                </tasks>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.17</version>
    <configuration>
        <!-- Fork every test because it will launch a separate AS 
instance -->
        <forkMode>always</forkMode>
        <systemPropertyVariables>
            <java.util.logging.manager>org.jboss.logmanager.
LogManager</java.util.logging.manager>
            <serverConfig>standalone-test.xml</serverConfig>
            <!-- the maven dependency plugin will have already 
downloaded the server on /target -->
            <jboss.home>${project.basedir}/target/
wildfly-9.0.2.Final</jboss.home>
            <module.path>${project.basedir}/target/
wildfly-9.0.2.Final/modules</module.path>
        </systemPropertyVariables>
        <redirectTestOutputToFile>false</redirectTestOutputToFile>
    </configuration>
</plugin>

Распаковка сервера и запуск тестов производится при сборке проекта с помощью Maven.

Возможные ошибки

В случае если при сборке выпадает ошибка “exception in thread” — есть вероятность что ошибка связана с недостатком памяти при сборке тестов, это можно исправить добавив следующий конфиг:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.17</version>
    <configuration>
        <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
...

так же в тег <argLine> можно добавить команды -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/dumps для создания дампа при падении сборки

Логирование

Логирование в тестах осуществляется стандартным логером WildFly. Уровень логов и директория вывода настраивается в standalone.xml в подсистеме логинга <subsystem xmlns=«urn:jboss:domain:logging:3.0»>.

Также можно использовать функционал maven-плагина Surefire по генерации отчетов об прохождении тестов, в отчетах будет присутствовать весь лог. Для этого нужно в конфигурации Surefire в теге <redirectTestOutputToFile>false</redirectTestOutputToFile>
false заменить на true. Репорты находятся в target/surefire-reports.

Работа с тестами

Тестовый класс обязан наследоваться от класса Arquillian. (В случае если используется JUnit можно заменить на аннотацию @RunWith(Arquillian.class))

Код тестового класса:

public class MockRepoExampleTest extends Arquillian {

    @Deployment
    public static Archive<?> createDeployment() {
        return ShrinkWrap.create(WebArchive.class, "mock-test.war") 
//test war name should be different for each test
            .addClass(TestEntityService.class)
            .addClass(ITestEntityService.class)
            .addClass(ITestEntityRepository.class)
            .addClass(TestEntity.class)
            .addPackage(PropertyManager.class.getPackage())

            //the way to add .properties to test war
            .addAsResource("global.properties")

            //fake repo below
            .addClass(MockTestEntityRepository.class)

            .addAsWebInfResource(EmptyAsset.INSTANCE, 
ArchivePaths.create("beans.xml"));
    }

    @Inject
    public ITestEntityService testEntityService;

    @Test
    @InSequence(1)
    public void shouldBeAbleToInjectCDI() {
        //test cdi injection works
        Assert.assertNotNull(testEntityService);
    }
}

Сборка архива

В начале теста находится метод с аннотацией @Deployment. В этом методе осуществляется сбор архива для теста, в данном случае .WAR (можно также собирать JAR — ShrinkWrap.create(JavaArchive.class, «test.jar»)).

В этот пакет должны быть включены все классы, интерфейсы и их зависимости, которые будут использованы в тесте. Например, в тесте приведенном выше класс TestEntityService использует внутри себя TestEntityRepository и реализует интерфейс ITestEntityService. В свою очередь класс TestEntityRepository реализует интерфейс ITestEntityRepository и использует TestEntity. Все эти классы и интерфейсы связаны между собой и все они должны быть добавлены в веб-архив. Если какой либо класс использует файл ресурсов или стороннюю библиотеку, то они тоже должны быть добавлены в архив.

Это делается вручную с помощью методов:

    .addClass(TestEntityService.class)
    // добавляет 1 класс в архив
    .addClasses(..)
    // позволяет добавить несколько классов в 1 методе
    .addPackage(TestEntity.class.getPackage())
    // добавляет весь пакет
    .addAsLybrary()
    //  позволяет добавлять сторонние библиотеки

Ресурсные файлы можно добавить в архив с помощью команд:

    .addAsResource("persistence-test.xml", "META-INF/persistence.xml")
    .addAsResource("exception_messages.properties")

В конце метода есть строка:

    .addAsWebInfResource(EmptyAsset.INSTANCE, 
ArchivePaths.create("beans.xml"));

Эта строка добавляет в веб-архив папку WEB-INF содержащую файл beans.xml.
это необходимо для тестирования EJB.

В случае необходимости развернуть несколько приложений в одном тесте можно применить такое решение:

    @Deployment(name = "dep1", order = 1)
    public static WebArchive createDep1() {}

    @Deployment(name = "dep2", order = 2)
    public static WebArchive createDep2() {}

Тестирование

Далее в тест передаются классы для тестирования:

    @Inject
    public IEntityTestService entityTestService;

Аннотация @Inject — стандартная аннотация CDI, в тесте работает точно так же как и в обычном коде. Также может быть использована аннотация @EJB.

Методы класса, осуществляющие тестирование помечаются аннотацией @Test.
Аннотация @InSequence(1) позволяет задавать порядок выполнения тестов.

    @Test
    @InSequence(1)
    public void shouldBeAbleToInjectCDI() {
    }

Mock тесты

Фреймворк позволяет производить mock-тестирование. Для этого в веб-архив нужно положить фейковые реализации классов. Пример такого теста приведен выше. В этом тесте производится тестирование класса TestEntityService. Класс использует объект TestEntityRepository внутри себя.

class TestEntityService implements ITestEntityService{
    @Inject
    private ITestEntityRepository repo;
    ...
}

class TestEntityRepository implements ITestEntityRepository{
    ...
}

Задача mock-теста заменить этот объект заглушкой, возвращающей заранее известные данные. Так как класс TestEntityRepository реализует интерфейс ITestEntityRepository, то для создания заглушки нам достаточно создать новый класс, например MockTestEntityRepository, реализующий этот интерфейс. После этого мы должны будем добавить этот класс в веб-архив (WAR-файл) вместо класса TestEntityRepository.

    .addClass(MockTestEntityRepository.class)

Полезная информация