Arquillian を利用したテスト

1. 本章の概要

Arquillianは、Jakarta EEアプリケーションのテスト・フレームワークです。

本章ではArquillian を利用して、Jakarta EEアプリケーションのテストを行う手順に関する 説明を行います。
アプリケーションは、mavenでビルドする前提であり、maven 3.0以上が必要です。

2. Managed モードと Remoteモード

WebOTX Arquillian では、Managed モードと Remote モードをサポートしています。

WebOTX Arquillian と同一マシン上の WebOTX Application Server を利用する場合は Managed モード、リモートマシン上で動作するWebOTX Application Server を利用する場合は Remoteモードを使用します。

3. テスト方法

ServletからEJBを利用するアプリケーションを例に、テストの進め方について説明します。

3.1. テスト対象の構成

以下の構成のアプリケーション(Servlet と EJB を含む)をテスト対象とします。

3.1.1. ディレクトリ構成

+---pom.xml
+---src
    +---main
    |   +---java
    |   |   \---sample
    |   |           Greeter.java
    |   |           GreeterServlet.java
    |   |
    |   +---resources
    |   \---webapp
    |   
    \---test
        +---java
        |   \---sample
        |           GreeterServletTest.java
        |           GreeterTest.java
        |
        \---resources
                arquillian.xml
    

3.1.2. EJBアプリケーション

EJB(Greeter.java)の実装例を以下に記載します。

    package sample;

    import javax.ejb.Stateless;
    import java.io.Serializable;

    @Stateless
    public class Greeter implements Serializable {
        private static final long serialVersionUID = 1L;

        public String greet() {
            return "Hello arquillian sample.";
        }
    }
    

3.1.3. Webアプリケーション

Webアプリケーション(GreeterServlet)の実装例を以下に記載します。

    package sample;

    import javax.ejb.EJB;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;


    @WebServlet(urlPatterns = "/greeter")
    public class GreeterServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;

        @EJB
        private Greeter greeter;

        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().append(this.greeter.greet());
        }
    }
    

3.1.4. pom.xml

pom.xml の定義例を以下に記載します。

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sample</groupId>
    <artifactId>hello-arquillian-sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jakarta.jakartaee-api.version>8.0.0</jakarta.jakartaee-api.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakarta.jakartaee-api.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

    

3.2. Arquillianテストを作成

3.2.1. 依存するライブラリの追加

Arquillian でテストするために pom.xml に追加が必要な dependency の定義について、以下に記載します。

Arquillian-JUnitインテグレーションを加えるために、以下の dependency の追加が必要です。

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

JUnit のテストコードを記述するために、以下の dependency の追加が必要です。

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest-all</artifactId>
        <version>1.3</version>
        <scope>test</scope>
    </dependency>
    

Remoteモードの場合は、以下の定義が必要です。

<dependency>
    <groupId>com.nec.webotx.arquillian.container</groupId>
    <artifactId>arquillian-webotx-remote</artifactId>
    <version>11.2.0</version>
    <scope>test</scope>
</dependency>    
    

Managed モードの場合は、以下の定義が必要です。

<dependency>
    <groupId>com.nec.webotx.arquillian.container</groupId>
    <artifactId>arquillian-webotx-managed</artifactId>
    <version>11.2.0</version>
    <scope>test</scope>
</dependency>    
    

ここまでに説明した dependency を追加すると、Remoteモードの場合の pom.xml は以下のようになります。

    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sample</groupId>
    <artifactId>hello-arquillian-sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <jakarta.jakartaee-api.version>8.0.0</jakarta.jakartaee-api.version>
        <junit.version>4.13.2</junit.version>
        <arquillian.version>1.6.0.Final</arquillian.version>
        <webotx-arquillian.version>11.2.0</webotx-arquillian.version>
        <hamcrest.version>1.3</hamcrest.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>jakarta.platform</groupId>
            <artifactId>jakarta.jakartaee-api</artifactId>
            <version>${jakarta.jakartaee-api.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.nec.webotx.arquillian.container</groupId>
            <artifactId>arquillian-webotx-remote</artifactId>
            <version>${webotx-arquillian.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>${hamcrest.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.arquillian.junit</groupId>
            <artifactId>arquillian-junit-container</artifactId>
            <version>${arquillian.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
    

3.3. Webアプリケーション(GreeterServlet) の テストコードを作成

src/test/java 配下に GreeterServletTest を作成します。
パッケージ名は sample とします。

package sample;


import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

import javax.servlet.annotation.WebServlet;

import static org.junit.Assert.*;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

@RunWith(Arquillian.class)
public class GreeterServletTest {

    @Deployment
    public static WebArchive getTestArchive() {
        final WebArchive war = ShrinkWrap.create(WebArchive.class, "hello-arquillian-sample.war")
            .addClasses(GreeterServlet.class, Greeter.class);
        return war;
    }

    @Test
    public void assertWarDeployed() throws Exception {
        final String servletPath = GreeterServlet.class.getAnnotation(WebServlet.class).urlPatterns()[0];

        final URLConnection response
            = new URL("http://localhost:8080/hello-arquillian-sample" + servletPath).openConnection();

        BufferedReader in = new BufferedReader(new InputStreamReader(response.getInputStream()));
        final String result = in.readLine();

        assertEquals("Hello arquillian sample.", result);
    }
}
    

太字部分は、Arquillian を利用するテストアプリケーションにおいて必須です。

@Deployment アノテーションを付加したpublic static メソッドでは、ShrinkWrapアーカイブを返す処理を行います。

addClasses メソッドでは、warファイルに含めるクラスを指定します。

URLのポート番号はテスト対象のアプリケーションが配備されているドメインの HTTP ポート番号を指定します。

Memo
テスト対象のwarやjarに含めているファイルはすべてArchiveを作成時に含める必要があります。
例えば、jspを含むWebアプリケーションなら、warファイルのArchiveにjspも含める必要があります。

3.4. 設定ファイルの作成

src/test/resources/arquillian.xml を作成し Arquillian の設定を行います。以下の定義例では、adminUser と adminPassword に環境変数の値を設定しています。

arquillian.xmlの定義例

<arquillian
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://jboss.org/schema/arquillian"
    xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd">

    <container qualifier="webotx-arquillian-remote"
        default="true">
        <configuration>
            <property name="adminHost">localhost</property>
            <property name="adminPort">6212</property>
            <property name="adminUser">${env.OTX_ADMIN_USER}</property>
            <property name="adminPassword">${env.OTX_ADMIN_PASSWORD}</property>
            <property name="encryptAdminPassword">true</property>
        </configuration>
    </container>
</arquillian>
    
arquillian.xmlの項目説明

arquillian.xml で設定可能な項目について、以下に記載します。

項目 設定値 既定値 Remote Managed 備考
adminHost 文字列 localhost WebOTXが動作しているホスト名/IPアドレス。
adminPort 1〜65535 6212 運用管理ポート。
protocol rmi/jmxmp rmi プロトコル(RMI/JRMPまたはJMXMP)。
secure true/false false SSLの設定。JMXMPの場合に適用。RMI/JRMPの場合、 false 固定。
tlsVersion 文字列 TLSv1.2 TLSバージョン。secure が trueの場合に使用する。
cipherSuites 文字列 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA 暗号化スイート。secure が trueの場合に使用する。
adminUser 文字列 - 運用管理ユーザ名。
adminPassword 文字列 - 運用管理ユーザのパスワード。
encryptAdminPassword true/false false adminPasswordが暗号化されているかを指定する。※adminPasswordには、開発者がencryptコマンドを使用し、暗号化したパスワードを生成し設定する。
useHttps true/false false テスト時に実行するリクエストにHTTPSを使用するかどうか。
webotxHome 文字列 - - WebOTXインストールディレクトリ。
domain 文字列 - - ドメイン名。${webotxHome}/domains/配下に存在すること。
debug true/false false - WebOTXをデバッグ・モードで起動するためのフラグ。
allowConnectingToRunningServer true/false false - Arquillianが既に実行中のWebOTXインスタンスに接続することを許可するフラグ。
deleteDeployFile true/false true 配備時に使用するアーカイブファイルを削除するかどうか。(問題発生時の調査ために配備したファイルを確認する等で使用することを想定。)
waitDynamicReflection 0〜 0 動的反映待ち合わせ時間。ミリ秒で指定。

3.5. パッケージ構成

ここまで作業した結果、パッケージエクスプローラに表示される構成は以下のようになります。

4. テストの実行

Remote モードの場合は、テスト実行前にテスト対象のアプリケーションを配備しているドメインを起動しておく必要があります。
まずは実行構成を設定します。

新規作成します。

プロジェクト、テストクラスを確認します。

「環境」タブを選択し、環境変数の設定を行います。

「実行」ボタンをクリックするとテストが実施されます。

4.1. EJBのテストコードを作成

続いて、EJB(Greeter.java) のテストコードを作成します。
src/test/java 配下に GreeterTest を作成します。
パッケージ名は sample とします。

package sample;

import static org.junit.Assert.*;

import javax.ejb.EJB;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(Arquillian.class)
public class GreeterTest {

    @EJB
    Greeter greeter;

    @Deployment
    public static JavaArchive getTestArchive() {
        final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "greeter-ejb.jar")
            .addClasses(Greeter.class);
        return jar;
    }

    @Test
    public void test() {
        assertNotNull(greeter);
        assertEquals("Hello arquillian sample.", greeter.greet());
    }
}
    

太字部分は、Arquillian を利用するテストアプリケーションにおいて必須です。

@Deployment アノテーションを付加したpublic static メソッドでは、ShrinkWrapアーカイブを返す処理を行います。

ShrinkWrap.create メソッドで、jarファイルを作成します。

addClasses メソッドでは、jarファイルに含めるクラスを指定します。

Memo
使用するプロトコル(http/https)の制御は useHttps で指定します。
既定では useHttps は false で http ポートを使用します。
https を使用する場合は、useHttps に true を指定します。
更に、defaultProtocol の設定で scheme に https を指定します。

4.2. WebOTX接続時に利用するパスワードの暗号化

パスワードを暗号化する場合は、encryptコマンドで暗号化したパスワードを生成します。以下に実行例を記載します。

    otxadmin> encrypt
    CLI261 暗号化する文字を入力してください:
    CLI262 もう一度入力してください:
    <暗号化されたパスワード>
    コマンド encrypt は正常に実行されました。
    

arquillian.xml の adminPassword に暗号化されたパスワードを設定します。

4.3. ドメインへの接続について

Arquillianではドメインから情報を取得したり、配備・配備解除を行うために JMX 通信を使用します。
JMX通信で使用するプロトコルをRMI/JRMPまたはJMXMPを protocol で指定します。
RMI/JRMP を使用する場合は、rmi(既定値) を、JMXMP を使用する場合は jmxmp を指定します。
JMXMP を使用する場合は、マニュアルの以下の箇所を参照しJMXMPを有効にしてください。

Application Server > 構築・運用 > ドメインの構築 1.12.1. JMXMPを利用する手順

JMX通信で SSL を使用する場合も JMXMP を使用してください。
次に JMXMP で SSL を使用する場合の arquillian.xml の設定を記載します。

    <pre class="source">
            :
    <container qualifier="webotx-arquillian-remote"
        default="true">
        <configuration>
            :
            <property name="protocol">jmxmp</property>
            <property name="secure">true</property>
        </configuration>
    </container>
    </pre>
            :

また、TLSバージョンや、暗号化スイートを既定から変更したい場合は、tlsVersion、cipherSuites で指定してください。

4.4. VM 上で動作している WebOTX を利用する場合について

ネットワークの接続設定がNAT接続の場合、ドメインの接続プロトコルとして、RMI/JRMP は利用できないため、JMXMP を使用します。

次の図の構成例の場合の対処方法を説明します

テスト対象のアプリケーションを配備しているドメインの設定にて、JMXMPプロトコルを有効にします。 マニュアルの以下の箇所を参照し、設定を行ってください。

Application Server > 構築・運用 > ドメインの構築 1.12.1. JMXMPを利用する手順

arquillian.xml の設定では、protocol にjmxmp を設定します。

WebOTX では JMXMP は既定で SSL を使用するようになっているため、WebOTX側の設定を変更していない場合は secure を true に設定します。

defaultProtocol の設定も必要であり、defaultProtocol の設定を行わない場合は以下のようなエラーが発生します。

java.lang.IllegalStateException: Error launching test sample.GreeterServletTest public void sample.GreeterServletTest.assertWarDeployed() throws java.lang.Exception
      :
Caused by: java.lang.IllegalStateException: Error launching request at http://localhost:8080/hello-arquillian-sample/ArquillianServletRunner?outputMode=serializedObject&className=sample.GreeterServletTest&methodName=assertWarDeployed. No result returned
    at org.jboss.arquillian.protocol.servlet.ServletMethodExecutor.executeWithRetry(ServletMethodExecutor.java:130)
    at org.jboss.arquillian.protocol.servlet.ServletMethodExecutor.invoke(ServletMethodExecutor.java:102)
    ... 79 more

arquillian.xml の設定例を以下に記載します。

            :
    <defaultProtocol type="Servlet 3.0">
        <property name="host">192.168.100.100</property>
        <property name="port">28080</property>
    </defaultProtocol>
    
    <container qualifier="webotx-arquillian-remote" default="true">
        <configuration>
            :
                        <property name="adminHost">192.168.100.100</property>
                        <property name="adminPort">26712</property>
                        <property name="protocol">jmxmp</property>
                        <property name="secure">true</property>
        </configuration>
    </container>
            :

5. ear形式のアプリケーションのテスト

ビルドで生成した EAR ファイルを取得し、テストクラスを追加する例を以下に示します。

5.1. 構成

+---pom.xml
+---auto-test
|   |   pom.xml
|   |
|   +---src
|   |   \---test
|   |       +---java
|   |       |   \---sample
|   |       |           AutoTest.java
|   |       |
|   |       \---resources
|   |               arquillian.xml
|   |
|   \---target
|           auto-test-1.0.jar
|
+---ear-module
|   |   pom.xml
|   |
|   \---target
|           ear-module-1.0.ear
|
+---ejb-module
|   |   pom.xml
|   |
|   +---src
|   |   +---main
|   |   |   +---java
|   |   |   |   \---sample
|   |   |   |           Greeter.java
|   |   |   |
|   |   |   \---resources
|   |   \---test
|   |       +---java
|   |       \---resources
|   \---target
|           ejb-module-1.0.jar
|        
\---war-module
    |   pom.xml
    +---src
    |   +---main
    |   |   +---java
    |   |   |   \---sample
    |   |   |           GreeterServlet.java
    |   |   |
    |   |   +---resources
    |   |   \---webapp
    |   \---test
    |       +---java
    |       \---resources
    \---target
            war-module-1.0.war

5.2. テストコードの作成

テストコードのサンプルソース(AutoTest.java)を以下に記載します。

package sample;

import static org.junit.Assert.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.api.Archive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class AutoTest {

    @ArquillianResource
    private URL deploymentUrl;

    @Deployment
    public static Archive<?> createDeployment() {
        EnterpriseArchive ear = ShrinkWrap.create(ZipImporter.class, "ear-module-1.0.ear")
                .importFrom(new File("../ear-module/target/ear-module-1.0.ear")).as(EnterpriseArchive.class);

        WebArchive web = ear.getAsType(WebArchive.class, "war-module-1.0.war");
        web.addClasses(AutoTest.class);

        return ear;
    }

    @Test
    public void assertRequest() throws Exception {
        final URLConnection response
            = new URL(deploymentUrl.toString() + "/greeter").openConnection();

        BufferedReader in = new BufferedReader(new InputStreamReader(response.getInputStream()));
        final String result = in.readLine();

        assertEquals("Hello arquillian sample.", result);
    }

}
    

ShrinkWrap.create でテスト対象のearファイルを指定します。

ear.getAsType でwarファイルを取得します。

addClasses でテストクラスをwarファイルに追加します。

6. 注意制限事項

6.1. 注意事項

6.1.1. rarファイルの利用

rar ファイルを含むアプリケーションをテストするには、earファイルにrarを含める必要があります。

6.2. 制限事項

なし