前回のブログに引き続きDeltaSpike Dataを使ったJava EEアプリケーションをArquillianを使ってテストする方法について説明します。DeltaSpike Data自身はJava EEサーバーに依存していませんが、今回のサンプルプログラムはJPAのpersistence.xmlがデータベースにアクセスするのにWildFlyのデータソースを使っているのでテストを実行するにはWildFlyが必要になります。このようなアプリサーバーに依存するテストはArqullianを使うことで可能になります。
Arquillianとは
Java EE アプリケーションは、REST/CDI/EJB/JMSなどサーバー上のコンテナに依存することが多いのでJUnitを使ったテストの自動化は簡単ではありません。
ArquillianはJava EEアプリケーションのためのテスティング・フレームワークです。アプリケーションをJava EEサーバー上で動作させてテストをしたいとき、以下のような処理をしたいことがあります。
- サーバーを起動する
- アプリケーションをサーバーにデプロイする
- サーバー上でテストを実行する
- アプリケーションをサーバーからアンデプロイする
- サーバーを停止する
Arquillianは、これらサーバーの起動・停止やデプロイ・アンデプロイを代わりにやってくれます。テストの実行は使い慣れたJUnit(やTestNG)を使います。しかも、JUNitのテストプログラムはサーバー上で実行され、CDIやEJBなどのBeanはテスト内にインジェクトして参照することができます。これにより、通常のJUnitを使ったテストと同様にテストをアプリサーバー上で実行することができます。
Arquillianテストプログラムの構造
Arquillianのテストプログラムは以下のような構造になります。
@RunWith(Arquillian.class) // (1)
public class MemberRegistrationTest {
@Deployment // (2)
public static Archive<?> createTestArchive() {
...
}
@Test // (3)
public void testRegister() throws Exception {
...
}
}
- (1) @RunWith()でTestRunnerとしてArquillian.classを指定します。
- (2) @DeploymentでアプリサーバーにデプロイするモジュールをShrinkWrap APIで作成します。
- (3) @Testでテストメソッドを指定します。
この@Deploymentの部分がArquillian固有の特徴のある部分です。Arquillianではアプリサーバー上でテストプログラムを実行するためのアーカイブをShrinkWrapというAPIを使ってプログラムで作成します。これはメモリ上でアーカイブを作る操作になります。このアーカイブにはアプリケーションのすべてのクラスを含めるのではなく、テストに必要なクラスだけ含めるようにします。このようにアーカイブを作ることでデプロイするアーカイブのサイズを小さくできますし、テストの用途に応じて、クラスやデプロイメントディスクリプタを差し替えることも可能です。
Arquillianのテストプログラム
前回のブログで作成したアプリケーションは、Mavenアーキタイプによって生成しましたが、それにはすでにArquillianに対応したテストプログラムが付いていました。それをベースにDeltaSpike Data用に修正します。修正のポイントは以下の通り。
- DataSpike Dataの依存ライブラリを追加する
- アーカイブにDeltaSpike Dataを使ったMemberRepository.classを追加する
- アーカイブにMETA-INF/apache-deltaspike.propertiesを追加する
注意:以下のテストプログラムでは直接MemberRepositoryを参照していませんが、MemberRegistrationのクラスにおいてそれをインジェクトしているために、アーカイブに含めておく必要があります。以下のサンプルではpom.xmlを参照して必要な依存ライブラリを抽出しています。
@RunWith(Arquillian.class)
public class MemberRegistrationTest {
@Deployment
public static Archive<?> createTestArchive() {
// DeltaSpike Dataの依存ライブラリをアーカイブに追加する
File[] files = Maven.resolver()
.loadPomFromFile("pom.xml")
//.importRuntimeDependencies().resolve()
.resolve("org.apache.deltaspike.modules:deltaspike-data-module-api", "org.apache.deltaspike.modules:deltaspike-data-module-impl")
.withTransitivity()
.asFile();
// アーカイブにテストプログラムが参照するクラスを追加する
Archive<?> archive = ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(Member.class, MemberRepository.class, MemberRegistration.class, Resources.class)
.addAsLibraries(files)
.addAsResource("META-INF/test-persistence.xml", "META-INF/persistence.xml")
.addAsResource("META-INF/test-apache-deltaspike.properties", "META-INF/apache-deltaspike.properties")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
// Deploy our test datasource
.addAsWebInfResource("test-ds.xml");
// デバッグ用にアーカイブの内容を標準出力に出力する
System.out.println("archive=" + archive.toString(true));
return archive;
}
@Inject
MemberRegistration memberRegistration;
@Inject
Logger log;
@Test
public void testRegister() throws Exception {
Member newMember = new Member();
newMember.setName("Jane Doe");
newMember.setEmail("jane@mailinator.com");
newMember.setPhoneNumber("2125551234");
memberRegistration.register(newMember);
assertNotNull(newMember.getId());
log.info(newMember.getName() + " was persisted with id " + newMember.getId());
}
}
Arquillianの実行
Arquillianは内部でアプリサーバーと通信してテストケースを実行しますので、テストを実行するにはアプリサーバーを実行する必要があります。アプリサーバーの利用方法としては、remote/managed/embeddedの3種類が可能です。今回はテスト実行時にアプリサーバーの起動・停止をおこなうmanagedモードを選択します。
managedでWildFlyと通信可能にするため環境変数JBOSS_HOMEを設定してください(または、arquillian.xmlにWildFlyのパスを書くことで環境変数を設定しないことも可能です)。
テストの実行方法はmavenコマンドのプロファイルとしてarq-wildfly-managedを指定します。
mvn clean test -Parq-wildfly-managed
このテストを実行すると自動的にWildFlyが起動され、テストプログラムがWildFly上で実行されます。このプログラムではデバッグ用にアーカイブを標準出力に出していますが、その結果が以下にようになっています。DeltaSpike Dataが内部で使っているDeltaSpike Coreなどのjarがいくつも追加されている点に注意してください。
$ mvn clean test -Parq-wildfly-managed
[INFO] Scanning for projects…
[INFO]
[INFO] ————————————————————————
[INFO] Building WildFly Quickstarts: tanoseam-webapp 1.0.0-SNAPSHOT
[INFO] ————————————————————————
[INFO]
// (略)
00:12:57,148 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: WildFly Full 10.0.0.CR4 (WildFly Core 2.0.0.CR8) started in 8679ms – Started 387 of 609 services (326 services are lazy, passive or on-demand)
archive=test.war:
/WEB-INF/
/WEB-INF/test-ds.xml
/WEB-INF/lib/
/WEB-INF/lib/deltaspike-partial-bean-module-impl-1.5.2.jar
/WEB-INF/lib/deltaspike-data-module-api-1.5.2.jar
/WEB-INF/lib/deltaspike-data-module-impl-1.5.2.jar
/WEB-INF/lib/deltaspike-core-impl-1.5.2.jar
/WEB-INF/lib/deltaspike-jpa-module-impl-1.5.2.jar
/WEB-INF/lib/deltaspike-proxy-module-api-1.5.2.jar
/WEB-INF/lib/deltaspike-proxy-module-impl-asm5-1.5.2.jar
/WEB-INF/lib/deltaspike-jpa-module-api-1.5.2.jar
/WEB-INF/lib/deltaspike-partial-bean-module-api-1.5.2.jar
/WEB-INF/lib/deltaspike-core-api-1.5.2.jar
/WEB-INF/classes/
/WEB-INF/classes/META-INF/
/WEB-INF/classes/META-INF/apache-deltaspike.properties
/WEB-INF/classes/META-INF/persistence.xml
/WEB-INF/classes/org/
/WEB-INF/classes/org/tanoseam/
/WEB-INF/classes/org/tanoseam/examples/
/WEB-INF/classes/org/tanoseam/examples/data/
/WEB-INF/classes/org/tanoseam/examples/data/MemberRepository.class
/WEB-INF/classes/org/tanoseam/examples/util/
/WEB-INF/classes/org/tanoseam/examples/util/Resources.class
/WEB-INF/classes/org/tanoseam/examples/service/
/WEB-INF/classes/org/tanoseam/examples/service/MemberRegistration.class
/WEB-INF/classes/org/tanoseam/examples/model/
/WEB-INF/classes/org/tanoseam/examples/model/Member.class
/WEB-INF/beans.xml// (略)
0:13:01,164 INFO [org.jboss.as] (MSC service thread 1-5) WFLYSRV0050: WildFly Full 10.0.0.CR4 (WildFly Core 2.0.0.CR8) stopped in 83ms
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 17.805 s
[INFO] Finished at: 2016-01-17T00:13:01+09:00
[INFO] Final Memory: 36M/395M
[INFO] ————————————————————————