Arquillianフレームワークを使ってJakarta EEアプリケーションをテストする方法

著者:Josh Juneau

2019年8月20日

Webアプリケーションやエンタープライズ・アプリケーションのテストは、Java SEプロジェクトのテストよりもはるかに面倒になる場合があります。スコープが異なり、ほとんどの場合にテストするフェーズも異なる、さまざまな種類のファイルが存在するからです。Arquillianは、長い年月をかけて進化してきた強力で堅牢な、Java EEおよびJakarta EE向けのテスト・フレームワークです。JBossプロジェクト(現在はRed Hatの一部)によって開発されたもので、テストやその出力を拡張できる複数のアドオン拡張機能もあります。

Arquillianフレームワークはとても強力ですが、その背景にはある程度の複雑さがあります。テスト・クラスの設定は、行う必要があるテストのレベルに応じて、とても簡単な場合もあれば、かなり複雑な場合もあります。

本記事では、Arquillianフレームワークの構成と、このフレームワークを使用したテストについて紹介します。基本的なJakarta EE 8(Java EE 8と同等)アプリケーションの構成とビルドに加え、JUnit 4とArquillianを使ういくつかのテストについても触れます。いくつかの基本的な例を示した後は、特に重要な機能や拡張機能について説明します。いずれも、Java EEおよびJakarta EEのプロジェクトの検証機能を拡張できるものです。本記事の内容を理解するためには、Java EEまたはJakarta EEプロジェクトの基本的知識が必要です。

サンプル・アプリケーションはMavenを使ってビルドしています。Mavenプロジェクトに対応した任意のIDEを使えるはずですが、本記事の例ではApache NetBeans IDEを使っています。GitHubから、ArquillianExampleという名前の、Maven Webアプリケーション・プロジェクトのサンプルを取得しておくことをお勧めします。

 

環境を設定する

Arquillianフレームワークを使う際に特に難しいことの1つが環境設定です。基本的なテストに必要となるのはわずかなMaven依存性だけですが、正しいバージョンの依存性を使わなければなりません。バージョンが正しくなかった場合、エラーにつながる不整合が起きる可能性があります。ゼロから始める場合は、コマンドラインまたはIDE(Apache NetBeansなど)からMavenアーキタイプを使ってMaven Webアプリケーション・プロジェクトを作成します。Apache NetBeansを使っている場合は、プラットフォームとしてJava EE 8を選んでください。

プロジェクトを作成したら、プロジェクトを右クリックしてPOMファイルを開き、リスト1に示す依存性を追加します。この依存性には、Arquillianの要件が含まれています(注:本記事のサンプル・ソース・コードを使用する場合、Jakarta EEプラットフォームとApache Derbyデータベースに関連する追加の依存性もあります)。

この依存性を眺めてみます。arquillian-bomアーティファクトがArquillianフレームワークの「部品表」になっており、すべての推移的依存性で使われます。POMファイルの標準の<dependencies>セクションには、JUnitフレームワークを追加しています。本記事の執筆時点では、まだJUnit 5はサポートされていないため、ここではJUnit 4を使っています。ただし、ArquillianでJUnit 5を使えるようにする作業も行われています。このAPIでJUnitを動作させるためには、Arquillian JUnitコンテナが必要です。お気に入りのテスト・フレームワークはTestNGだという方のために、このフレームワークを使えるコンテナも存在します。JBoss ShrinkWrap Resolverを使用して、ShrinkWrapをサポートしています。ShrinkWrapにより、コードから宣言的にデプロイメント・アーカイブを作成できます。その他の依存性は、arquillian-weld-ee-embeddedという名前の埋込みCDIコンテナにテスト・アーカイブをデプロイするために必要となります。実現しようとしている、テストのレベルに応じて、いくつかのデプロイメント・オプションが存在します。Weld埋込みコンテナは、データベース接続や、アプリケーション・サーバー・コンテナのその他の機能を使用せずにCDIをテストする場合に便利です。埋込みコンテナを活用することで、起動とテストがスムーズになります。データベース接続テストを実行する必要がある場合や、その他のアプリケーション・サーバー機能を使う必要がある場合は、リモート・コンテナにデプロイすることも可能です。後ほど、GlassFishサーバーに接続する方法も説明します。

 

小さなアプリケーションを書く

この例で使うアプリケーションは、いくつかの小さなデータベース表を操作して、水泳プール会社とその顧客に関するレコードの作成、読取り、更新、削除(CRUD)を行うことができるというものです。Apache NetBeansやその他ほとんどのIDEでは、ウィザードを使用して、非常に短時間でアプリケーションを生成することができます。そのため、アプリケーションの初期生成は省略し、アプリケーション・ロジックのテスト部分を中心に説明します。

Poolはデータベースに格納でき、Customerオブジェクトに関連付けられています。本記事では、Java Persistence API(JPA)とEJB Beanを使って、Poolデータベース操作の単体テストを作成します。また、CDI Beanのテストについても説明します。このBeanでは、データベースを操作するEJB Beanにフロントエンドをバインドするビジネス・ロジックを実行します。なお、EJB BeanをRESTfulサービス呼出しに置き換えることもできる点に注意してください。その場合も、テストは同じように行われます。

 

基本的なCDI単体テストを書いて実行する

それでは、とても簡単なCDIテストから始めます。この基本的なテスト用のCDI Beanでは、単純にCDI注入を使用して、Poolオブジェクトが適切に作成されていることを確認するテストを行います。テストを書く前に、テスト・クラスを適切に設定する必要があります。

一般的に、アプリケーションのビジネス・ロジックを含む各クラスに対応するテスト・クラスが存在する必要があります。この例でのテスト・クラスは、PoolControllerTestという名前です。このテスト・クラスからArquillianを利用する場合、クラス宣言の前に@RunWith(Arquillian.class)アノテーションを付加する必要があります。このアノテーションを使うと、Arquillianエンジンを使ってテストを実行するようテスト・フレームワークに伝えることができます。

各クラスには、指定したすべてのクラスやライブラリ、構成ファイルをテスト・パッケージ(WAR、EAR、JAR)に格納するデプロイメント・メソッドも必要です。このテスト・パッケージは、ビルド後、Arquillianエンジンによって選択済みテスト・コンテナに自動的にデプロイされ、実行されます。その際の出力は、コマンドラインまたはIDEに表示されます。

デプロイメント・パッケージを作るためには、@Deploymentアノテーションを付加したパブリックな静的メソッドからShrinkWrapアーカイブを返します(リスト2にこれを示します)。このデプロイメント・メソッドの中では、Builderパターンを使ってデプロイ可能アーカイブを構築します。その際に、作りたいアーカイブの種類を指定してShrinkWrapcreate()メソッドを呼び出します。今回の例では、WebArchive.classを作成しています。別の種類のアーカイブには、標準JARを作るJavaArchive、EARファイルを作るEnterpriseArchiveなどがあります。テストで使う各クラスは、CLASSPATHに追加する必要があります。この追加は、addClass()addClasses()addPackage()addPackages()の各メソッドを状況に応じて使用し、行います。また、addAsManifest()メソッドも呼び出し、beans.xmlファイルを追加してCDIを起動できるようにしている点に注意してください。デプロイ用のcreate()ビルダーの一部であるさまざまなメソッドの詳細については、ShrinkWrapのチュートリアルをご覧ください。

リスト3には、PoolControllerのソース・コードが含まれています。コンストラクタでは、2つのPoolオブジェクトを作成してArrayListに追加しています。このArrayListに対して、空でないことを確認するテストを行っています。CDIコントローラは、標準の@Injectアノテーションを使ってテスト・クラスに注入できます。この注入処理は、テスト・メソッドの直前に行う必要があります。このテストではJUnitを使うことから、テスト・メソッドに@Testアノテーションを付加します。また、テスト・メソッドでは、org.junit.Assertで定義されているアサーション・メソッドのいずれかを使って、テストが成功したかどうかを判定します。テスト・クラスの完全なソース・コードは、リスト4をご覧ください。他のJUnitテストと同じように、ブール値を返す式のテストに使用できるAssertionメソッドも複数存在します。選択できるAssertionメソッドのリストについては、JUnitのドキュメントをご覧ください。

IDEまたはコマンドラインからMavenビルドを実行するだけで、テストを実行できます。プロジェクトをビルドするたびにテストが実行されます。テストのみを実行するためには、コマンドラインからmvn testコマンドを使います。Mavenを使ってプロジェクトをビルドする場合や、コマンドラインからテストを実行する場合は、埋込みWeldコンテナ(このプロジェクト用に選択したデプロイメント・コンテナ)でテストが実行されます。テストを実行すると、次のような内容が出力されます。

Tests run:1, Failures:0,
 Errors:0, Skipped:0
 org.javamagazine.arquillianexample.cdi.PoolControllerTest
Results :
Tests run:1, Failures:0, Errors:0, Skipped:0
 

JPAとGlassFishを構成する

実際のデータベースに対してテストを行うためには、いくつかのことを決める必要があります。PersistenceContextは、テスト・クラスに直接注入できます。ただし、トランザクションを管理するためにUserTransactionも注入する必要があります。この場合、テスト・クラス自体がCDIコントローラとして扱われます。このタイプのテストは、次のようになります。

@Test
public void insertData(){
    utx.begin();
    em.joinTransaction();
    Pool pool = new Pool();
    // 値の設定
    em.persist(pool);
    utx.commit();
    em.clear();
}

本記事では、PersistenceContextは使わずに、リモートGlassFishサーバーにデプロイしてテストを行います。同じ依存性で動作するため、GlassFishの代わりにPayaraを使うこともできます。また、他のいくつかのサーバーを使うこともできます。リモート・サーバーに対してテストを行うことで、多くのメリットが生じます。たとえば、テスト環境ではなく実環境でテストができることや、言うまでもありませんが、本番環境でのユースケースに一致するように、データベース接続プールやサーバー環境設定を構成できることです。

リモート・サーバーに対してテストを行う場合、テストを実行する前にサーバーが稼働していなければなりません。そうでない場合、テストは失敗します。サーバーはArquillianエンジンの外にあるため、テストを実行するマシンと同じマシンにインストールされていても、「リモート」サーバーとして構成する必要があります。別の方法として、実際にリモートのサーバーを構成してテストを実行することもできます。

リモート・サーバー環境を設定するためには、src/test/java/resources領域に追加の設定が必要です。また、リスト5に示すような、arquillian-glassfish-remote-3.1への依存性をPOMファイルに追加する必要があります。GlassFishサーバーにログインするための資格証明を、リスト6に示すようにarquillian.xmlファイルに記述する必要があります。

コンテナ設定の属性の1つに、adminHostがある点に注意してください。この属性は、必要に応じて実際のリモート・サーバーに向けることができます。テスト・データベースを使用してテストを行えるように、test-persistence.xmlファイルをこのリソース領域に設定しておくとよいでしょう。test-persistence.xmlという名前の永続コンテキストを作成し、プロジェクトのsrc/test/java/resourcesフォルダに配置します。このファイルには、リモート・アプリケーション・サーバー・コンテナ内で構成されるデータソース用のJava Transaction API(JTA)接続構成を含める必要があります。

これでリモートGlassFishサーバー用の構成が整いました。次は、EJB Bean CustomerFacadeのテストを作成します。EJBテストは、src/test/javaフォルダに配置し、org.javamagazine.arquillianexample.session.CustomerFacadeTestという名前にする必要があります。CustomerFacadeTestのソース・コードをリスト7に示します。このテスト・クラスの宣言の前には、標準の@RunWith(Arquillian.class)アノテーションを含める必要があります。また、このクラスには、ShrinkWrapアーカイブを返すために、@Deploymentアノテーションを付加した静的メソッドが含まれている必要があります。

デプロイメント・メソッドは、createDeployment()という名前とし、そこには、EJBテストを実行するためにCLASSPATHに追加する必要がある各クラスへの参照を含めています。このデプロイメントでは、テスト・デプロイメント・アーティファクト内のCDIを構成するために、ビルダー・パターンを使って

addAsManifestResource(EmptyAsset.INSTANCE,"beans.xml")

を呼び出しています。このデプロイメントには、永続性コンテキストを構成するために、

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

の呼出しも含めています。

この場合、test-persistence.xmlファイルが、テスト・デプロイメントの標準のpersistence.xmlファイルとして読み込まれます。CustomerFacadeTest内のテスト・メソッドは非常にシンプルです。findAllCustomers()メソッドがCustomersエンティティの問合せを行ってその結果を返したかどうかを判定するために、Assert.assertTrueを呼び出しています。

実際のアプリケーションでは、EJB Beanを直接呼び出すのではなく、フロントエンドからCDIコントローラCustomerControllerを呼び出します。そこで、CustomerController CDI Beanが適切にアプリケーション・ロジックを呼び出して画面に結果を返すことを確認するテストを行う必要があります。リスト8に示すように、CDIコントローラのテストは、org.javamagazine.arquillianexample.cdi.CustomerControllerTestを使用して行います。EJBのテストとよく似ていますが、このテストではCustomerController getCustomerList()メソッドを呼び出し、メソッドに移入が行われていることを確認しています。インスタンスが生成される際に、CustomerControllerクラスによってcustomerListが移入されるため、このテストからはtrueという結果が返されるはずです。

 

テスト実行のオプション

テストは、コマンドラインまたはIDEから実行できます。テストするファイルごとに別々にテストを実行することも、プロジェクトのすべてのテストをビルド・プロセスに利用することもできます。コマンドラインから1つのファイルのテストを実行するためには、コマンドラインを開いてプロジェクトのディレクトリに移動し、次のコマンドを発行します。

mvn test

テストの結果は即座に表示されます。X個のテストが設定されている場合、X回成功する必要があります。Mavenビルドの一部としてテストを実行している場合、いずれかのテストが失敗したときは、ビルドも失敗するため、アプリケーションのパッケージ化は行われません。

視覚的なフィードバックを受け取りたい場合は、IDEでテストを行うとよいでしょう。NetBeans IDEでは、テスト・ファイルを右クリックするか、図1のようにプロジェクト自体を右クリックして「Test File」を選択することで、テストを実行できます。

NetBeans IDE内で1つのテストを実行する

図1:NetBeans IDE内で1つのテストを実行する

NetBeans内でテストを実行した場合、テストの結果がTest Resultsパネルに表示されます。複数のテストがある場合、図2のように、失敗した各テストがパネルに表示されるため、そこから結果を解析して修正を行うことができます。パネルの左側には、簡単にテスト結果を移動するボタンや、テストを再実行するボタンもあります。

Apache NetBeansのTest Resultsパネル

図2:Apache NetBeansのTest Resultsパネル

 

複数のコンテナによるテスト

別のコンテナでテストを行えるようにプロジェクトを構成するためには、POMファイルでMavenプロファイルを宣言することを検討します。Mavenプロファイルを使うことで、各コンテナに対する依存性をそれぞれ分離し、プロジェクトのビルド時に希望のプロファイルを選択することができます。また、リスト9に示すように、POMファイルでactiveByDefault要素をtrueに設定し、デフォルトのプロファイルを指定することも可能です。

各プロファイルには、コンテナに対する依存性が含まれています。activeByDefault要素を変更して、アクティブなコンテナを切り替えることができます。なお、IDEの中には、ユーザー・インタフェースで複数のMavenプロファイルから選択できるものもあることに注意してください。

 

高度な内容

Arquillianフレームワークには、テスト・クラスで使用して、テストの調整や実行の制御をさらに行うことができるテスト補助機能が多数含まれています。たとえば、先ほどの例でお見せしたように、注入ポイントは、@Injectアノテーションを使ってフィールドに作成することも、テスト・メソッドで引数として宣言して直接作成することもできます。

こちらも例で示しましたが、テスト・クラスにセッションBeanを注入するために、@EJB補助機能を使用します。その他の補助機能には、@PersistenceContext@PersistenceUnit@Resourceなどがあります。最初の2つは、永続コンテキストと永続ユニットを示します。@Resourceアノテーションは、Java Naming and Directory Interface(JNDI)で利用できるオブジェクトの注入に使用します。他の利用可能なJNDIリソースに対するリモート・コンテナ・テストを行う場合、このオプションが非常に役立ちます。

テストは、コンテナ・モードとクライアント・モードという2つのモードで実行できます。デフォルトはコンテナ・モードです。このモードでは、Arquillianのサポート・クラスを追加して@Deploymentを再パッケージ化することが可能なため、コンテナ内でリモート・テストを行うことができます。 クライアント・モードを選択した場合、@Deploymentの再パッケージ化やリモート・サーバーへのデプロイは行われません。JVM内でテスト・ケースが実行されるため、クライアントが外側からコンテナをテストすることができます。クライアント・モードで実行するためには、静的デプロイメント・メソッドに次のアノテーションを付加します。

@Deployment(testable=false)

コンテナのテストとクライアントのテストの両方を実行するためには、デプロイメントでtestable=trueを宣言し、クライアント・モードで実行するテスト・メソッドに@RunAsClientアノテーションを付加します。クライアントとして実行する理由の1つに、Warp拡張機能を使ってフロントエンドのテストを行えるようにする点が挙げられます。Warp拡張機能を使うことで、クライアントサイドのテストを書いて、サーバーサイドのロジックを検証することができます。これにより、クライアントとサーバーの両方を使って行う結合テストが可能になります。

 

拡張機能

複数のArquillian拡張機能をArquillianと組み合わせることにより、さまざまなタイプのテストを実現できます。前述のとおり、Warp拡張機能を使用して、サーバーサイドのロジックを検証する、クライアントサイドのテストを書くことができます。よく使われる別の拡張機能に、Grapheneがあります。Grapheneは、JavaServer Faces(JSF)やSpring MVCで構築したフロントエンドをテストする場合に特に便利でしょう。Grapheneでは、Selenium WebDriverを使ってプログラム的な方法でブラウザを操作することができます。そのため、Javaコードを使用してフォームに入力することや、ビジネス・ロジックによってWebページを移動すること、そしてアサーションで動作のテストを行うことができます。その過程でコンテンツを検証し、UI内で適切な機能が実現されていることを確認することができます。Selenium WebDriverを使用する別の拡張機能であるDroneでは、ブラウザのライフサイクルを管理し、ブラウザをテスト・クラスに注入することができます。Graphene拡張機能はDroneとSeleniumに依存しているため、プロジェクトにいくつかの依存性を追加して、これらの拡張機能を利用できるようにする必要があります。Grapheneテストの@Deploymentは、「クライアント」モードで実行する必要があります。つまり、testable=false属性を含める必要があります。ユーザー・インタフェースのテスト用のデプロイメント・パッケージには、テストされるそれぞれのビューを含める必要があります。そのため、アプリケーションにcustomer.xhtmlという名前のビューが含まれている場合、次のようにして、@Deploymentメソッドでこのリソースを追加する必要があります。

.addAsWebResource("/customer.xhtml");

Grapheneにより、マークアップではなくJavaを使って、別々のページおよびそれらのページに含まれる個々の機能をテストすることが可能になる「ページ・オブジェクト」および「ページ断片」を作成できます。本記事ではこういった考え方について詳しくは説明しませんが、テストのニーズを満たせるように、このような機能を確認しておくことを強くお勧めします。

 

まとめ

Arquillianフレームワークにより、エンタープライズ・アプリケーションでおもに利用されるコンポーネントのテストが実現します。そのため、Java EE/Jakarta EE開発者にとって欠かせないツールです。このフレームワークでは、的を絞った細かいテストをサポートしているため、選択したクラスや機能のみをテスト・デプロイメントに含めることができます。また、JSF、REST、サーブレット、Spring MVCなど、エンタープライズ開発で使用されるテクノロジーのテストを実現する多くの拡張機能が作成されている、成熟したテスト・フレームワークでもあります。

 

Java Magazine December 2019の他の記事

プロパティベース・テストを習得する
ArchUnitでアーキテクチャの単体テストを行う
新しいJava Magazine
作ってみよう:自分だけのテキスト・エディタ(パート1)
クイズに挑戦:Collectorsの使用(上級者向け)
クイズに挑戦:ループ構造の比較(中級者向け)
クイズに挑戦:スレッドとExecutor(上級者向け)
クイズに挑戦:ラッパー・クラス(中級者向け)
書評:Core Java, 11th Ed. Volumes 1 and 2


Josh Juneau

Josh Juneau(@javajuneau):アプリケーション開発者、システム・アナリスト、データベース管理者。おもに、Javaやその他のJVM言語を使った開発に従事。Oracle Technology NetworkやJava Magazineで多くの記事を執筆し、JavaやJava EEに関する複数の書籍をApressから出版している。JSR 372およびJSR 378のJCP専門家グループのメンバーを経験。NetBeans Dream Teamメンバー、Java Champion、CJUG OSS Initiativeのリーダーであり、JavaPubHouse Off Heapポッドキャストにレギュラー出演中。