CDIを使ったプログラムの単体テスト(1)

これから何回かに分けてCDIベースのテストを支援するDeltaSpikeのモジュールを紹介します。今回はDeltaSpike Test-Controlモジュールです。

CDIベースの単体テストの問題

CDIを使ったインジェクション、プロデューサ、インタセプタ、イベントなどを使ったクラスをどうやってテストしたらよいのでしょう。setterインジェクションを使うなど、無理をすればある程度はコンテナ無しでもテストができるかもしれませんが、できればCDIの普通の使い方をしたままでテストをしたいものです。そうなると、JUnitとCDIコンテナを連携させる仕組みが必要になります。

テスト対象がEJBやコンテナ管理のJPAを使っている場合など、Java EEコンテナに依存する場合には、Arquillianを使うのが定番ですが、Arquillianはテストをアプリサーバー上で実行するためにアプリサーバーの起動・停止が必要となり、どうしてもテストの実行時間が長くなる傾向があります。

ここで紹介するDeltaSpike Test-Controlモジュールは、CdiTestRunnerというJUnitのTestRunnerとして提供されるモジュールで、JUnitと組み合わせて使うことで、Java SE上でCDIのテストができます。CdiTestRunnerは、ArquillianのようにJava EEの機能を使ったテストはできませんが、Java SE上でのテストですのでArquillianに比べて短時間にテストを実行することができるというメリットがあります。

CdiTestRunnerの使い方

使い方は簡単です。まず、Mavenでプロジェクトを作って、以下のような依存ライブラリを設定したpom.xmlファイルを設定します。このpom.xmlではCDIコンテナとしてWeldを使うことを想定していますが、他のコンテナを使うときにはマニュアルを参照してください。また、バージョンについては以下のサンプルでは現時点での最新バージョンを指定しましたが、お使いのプロジェクトに合わせて変更してください。

<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>org.tanoseam.example</groupId>
	<artifactId>tanoseam-example</artifactId>
	<version>1.0-SNAPSHOT</version>

	<properties>
		<junit.version>4.12</junit.version>
		<weld.version>2.3.1.Final</weld.version>
		<deltaspike.version>1.5.1</deltaspike.version>
	</properties>

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

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-test-control-module-api</artifactId>
			<version>${deltaspike.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.deltaspike.modules</groupId>
			<artifactId>deltaspike-test-control-module-impl</artifactId>
			<version>${deltaspike.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.deltaspike.cdictrl</groupId>
			<artifactId>deltaspike-cdictrl-weld</artifactId>
			<version>${deltaspike.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.jboss.weld.se</groupId>
			<artifactId>weld-se-core</artifactId>
			<version>${weld.version}</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
</project>

次に、テストクラスでは@RunWithにCidTestRunnerを指定します。以下はTest-Controlの使用例を示すために作ったインジェクションを使った簡単なサンプルです。

package org.tanoseam.examples;

import org.junit.Test;
import org.junit.runner.RunWith;
import javax.inject.Inject;
import org.apache.deltaspike.testcontrol.api.junit.CdiTestRunner;

@RunWith(CdiTestRunner.class)
public class CdiUnitTest {
		
	@Inject
    private MyBean myBean;
	
	@Test
	public void testLog() {
      myBean.log("hello");

    }
}

CdiUnitRunnerはRequestScope/SessionScope/ApplicationScopeをサポートしています。このテストがインジェクトしているMyBeanはSessionScopeのBeanです。このMBeanは、さらにApplicationScopeのLoggingBeanというBeanをインジェクトしています。

package org.tanoseam.examples;

import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;

@SessionScoped
public class MyBean implements Serializable {
	
	@Inject 
	LoggingBean loggingBean;

	public MyBean() {}

	@PostConstruct
	public void initialize() {
		log("MyBean::initialize");
	}

	@PreDestroy
	public void destroy() {
		log("MyBean::destroy");
	}

	void log(String message) {
		loggingBean.log(message);
	}
}

LogginBeanの定義は次のようになります。

package org.tanoseam.examples;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class LoggingBean {
	
	public LoggingBean() {}

	@PostConstruct
	public void initialize() {
		log("LoggingBean::initialize");
	}

	@PreDestroy
	public void destroy() {
		log("LoggingBean::destroy");
	}

	void log(String message) {
		System.out.println("CDI: " + message);
	}
}

最後に忘れてはいけないのがbeans.xmlです。以下のファイルをMETA-INF/beans.xmlに配置してください。ここではbean-discovery-modeとして”annotated”を指定しています。このモードのときはCDIコンテナはスコープアノテーションがついてるクラスしかスキャン対象にしませんので注意してください。

※ beans.xmlにversion=”1.1″を付け忘れていたので追記しました(11/02)

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" 
version="1.1" 
bean-discovery-mode="annotated">
</beans>

このテストを実行するとコンソールに以下のように表示されます。実際にはWeldの起動や停止の部分のログが大量に出力されますが、ここではテストプログラムの出力前後のみを掲載します。

10 31, 2015 4:25:19 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testStarted
情報: [run] org.tanoseam.examples.CdiUnitTest#testLog
CDI: LoggingBean::initialize
CDI: MyBean::initialize
CDI: hello
10 31, 2015 4:25:19 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testFinished
情報: [finished] org.tanoseam.examples.CdiUnitTest#testLog
CDI: MyBean::destroy
CDI: LoggingBean::destroy
10 31, 2015 4:25:19 午後 org.jboss.weld.environment.se.WeldContainer shutdown

CdiTestRunnerでは、RequestScopeやSessionScopeのBeanはテストごとに作成されます。上のテストでは、MyBeanはSessionScopedなのでテストごとに生成されますが、LoggingBeanはApplicationScopeなので、一回作成されると複数のテスト実行中に同じものが使いまわされます。

ここでスコープの違いによるBeanのライフサイクルの違いを見るためにテストプログラムの一部を以下のように変更します。ご覧の通り、単に同じテストを2回実行しているだけです。

	@Test
	public void testLog() {
      myBean.log("myBean=" + myBean);
	 //myBean.log("hello");
    }
	
	@Test
	public void testLog2() {
	  myBean.log("myBean=" + myBean);
    }

実行後のコンソールログを見ると、LoggingBean::initializeとLoggingBean::destroyはそれぞれテストの先頭と最後にしか呼び出されていないのに対し、MyBean::initializeとMyBean::destroyはテストメソッドの呼び出しごとに呼び出されていることが確認できます。

10 31, 2015 4:36:23 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testStarted
情報: [run] org.tanoseam.examples.CdiUnitTest#testLog
CDI: LoggingBean::initialize
CDI: MyBean::initialize
CDI: myBean=org.tanoseam.examples.MyBean@67c33749
10 31, 2015 4:36:23 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testFinished
情報: [finished] org.tanoseam.examples.CdiUnitTest#testLog
CDI: MyBean::destroy
10 31, 2015 4:36:23 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testStarted
情報: [run] org.tanoseam.examples.CdiUnitTest#testLog2
CDI: MyBean::initialize
CDI: myBean=org.tanoseam.examples.MyBean@fa49800
10 31, 2015 4:36:23 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testFinished
情報: [finished] org.tanoseam.examples.CdiUnitTest#testLog2
CDI: MyBean::destroy
CDI: LoggingBean::destroy
10 31, 2015 4:36:23 午後 org.jboss.weld.environment.se.WeldContainer shutdown

CDIのDI(Dependency Injection)は、もともとBean間を疎結合連携をさせるための仕組みです。アプリケーション全体としてはJava EE上でないと動作できない場合であっても、それを構成する個々のBeanについてはJava SE上で単体テストが十分可能な場合もあるでしょう。今回紹介したCdiTestRunnerはそのような場合に活用できると思います。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。