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

前回のブログに引き続きJava SE上でCDIプログラムの単体テストをする方法について書きます。今回はDeltaSpike Coreモジュールに含まれるProjectStageと@Exclude、そして最後にCdiTestRunnerと組み合わせて使う方法を紹介します。

ProjectStageとは

単体テスト、結合テスト、システムテストなど、テストのフェーズ応じてテスト用のプログラムやデータを切り替えたいということがあります。単体テストの場合はDerbyのような軽量なDBを使い、システムテストでは本番と同等の構成のDBを使うというような場合です。この切り替えのために手作業で設定ファイルを変更するのでは自動ビルドに対応するのが難しくなるので、プログラム起動のパラメーターによって切り替えられる仕組みが望まれるところです。

DeltaSpikeのProjectStageは、プロジェクトの開発ステージを実行時に指定できるようにすることで、指定されたステージに応じた処理を書けるようにするものです。ProjectStageが提供する既定義のステージとして以下が提供されています。

  • UnitTest
  • Development
  • SystemTest
  • IntegrationTest
  • Staging
  • Production

実行時にどのステージかを指定するには、次のようにJavaプログラムの起動時にシステムプロパティ(org.apache.deltaspike.ProjectStage)を設定します。

-Dorg.apache.deltaspike.ProjectStage=UnitTest

起動されたプログラム側では指定されたステージを@Injectで取得することができるので、その値にもとづいて起動のときに指定されたステージを知り、そのステージに対応した処理を書くことができます。

@Inject
ProjectStage projectStage;

// ...

if (ProjectStage.UnitTest.equals(this.projectStage)) {
    // 単体テストのステージでの処理
} else {
    // その他のステージでの処理
}

ProjectStageによる@Excludeの制御

DeltaSpikeの@Excludeは、指定されたクラスをインジェクションの対象から明示的に外すためのアノテーションです。CDI 1.1仕様では@Vetoedという同じ目的のアノテーションがありますが、@Excludeは、アノテーションの引数としてインジェクションの対象から外す条件を指定できるのが特徴です。そして、このときの条件としてProjectStageを使うことができます。

具体例を見てみましょう。MessageSenderというインタフェースを型として変数にインジェクトするシーンを考えます。

@Inject
MessageSender messageSender;

MessageSenderはメールを送信するBeanです。これを利用するBeanのテストを考えたとき、単体テストではMessageSenderにはメール送信の代わりにログを書くだけにしてもらいたいと思います。このようなときにProjectStageを使って実行時に指定されたステージに従って、単体テストのときと、それ以外のときでインジェクトされるMessageSenderを切り替えることが可能です。

次のプログラムは単体テスト以外の場合にインジェクトされるべきクラスです。@Exclude(ifProjectStage =<ステージクラス>)とすると、指定されたステージの場合にはそのクラスが排除されます。

package org.tanoseam.examples;

import javax.enterprise.context.ApplicationScoped;
import org.apache.deltaspike.core.api.exclude.Exclude;
import org.apache.deltaspike.core.api.projectstage.ProjectStage;

@ApplicationScoped
@Exclude(ifProjectStage = ProjectStage.UnitTest.class)
public class MessageSenderImpl implements MessageSender {

	public MessageSenderImpl() {}

	public void send(String message) {
		System.out.println("MessageSenderImpl: " + message);
	}
}

次に、単体テストのときにインジェクトされるクラスの定義です。指定されたステージでインジェクトされないことを設定するには@Exclude(exceptIfProjectStage = <ステージクラス>)とします。
「〜以外のステージの場合はインジェクションの対象としない」ということは、二重否定の書き方で少々わかりにくいのですが、指定されたステージではインジェクションの対象になるいうことです。

package org.tanoseam.examples;

import javax.enterprise.context.ApplicationScoped;
import org.apache.deltaspike.core.api.exclude.Exclude;
import org.apache.deltaspike.core.api.projectstage.ProjectStage;

@ApplicationScoped
@Exclude(exceptIfProjectStage = ProjectStage.UnitTest.class)
public class TestMessageSenderImpl implements MessageSender {
	
	public TestMessageSenderImpl() {}
	
	public void send(String message) {
		System.out.println("TestMessageSenderImpl: " + message);
	}
}

DeltaSpike Coreモジュールの使い方

ここまで説明してきたProjectStageと@Excludeは、JUnitのようなテスティングフレームワークとの併用が必須というわけありません。プログラム起動時にシステムプロパティでステージを指定すれば、実行時にステージ情報を取得でき、それに従って@Excludeが動作します。以下はJava SE上でWeldを使ったサンプルになります。

package org.tanoseam.examples;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.apache.deltaspike.core.api.projectstage.ProjectStage;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

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

	@Inject
	ProjectStage projectStage;
	
	@Inject
    MessageSender messageSender;

	public void testSend() {
		messageSender.send("ProjectStage=" + projectStage);
    }
	
	static public void main(String args[]) {
		Weld weld = new Weld();
	    WeldContainer container = weld.initialize();
	    container.instance().select(ProjectStageExample.class).get().testSend();
	    weld.shutdown();
	}
}

ProjectStageと@Excludeは、DeltaSpike Coreモジュールに含まれます。このモジュールはMaven pom.xmlに以下の依存モジュールを設定することで有効になります。

 	<dependency>
            <groupId>org.apache.deltaspike.core</groupId>
            <artifactId>deltaspike-core-api</artifactId>
            <version>${deltaspike.version}</version>
            <scope>compile</scope>
	</dependency>
		
	<dependency>
            <groupId>org.apache.deltaspike.core</groupId>
            <artifactId>deltaspike-core-impl</artifactId>
            <version>${deltaspike.version}</version>
            <scope>runtime</scope>
	</dependency> 

上のプログラムではWeldをテストではなく、実行時に使っているので、Weldの依存ライブラリのスコープをcompileにしておいてください。

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

それから、CDIを使うので前回のブログと同様にMETA-INF/beans.xmlが必要になります。忘れずにファイルを作成してください。

準備ができたらシステムプロパティorg.apache.deltaspike.ProjectStageでステージをいくつか変更して実行してみましょう。ステージにUnitTestを指定すると、messageSender変数に次のようにテスト用のMessageSenderがインジェクトされます。

$ mvn clean install exec:java -Dexec.mainClass=”org.tanoseam.examples.ProjectStageExample” -Dorg.apache.deltaspike.ProjectStage=UnitTest

INFO: WELD-ENV-002003: Weld SE container STATIC_INSTANCE initialized
TestMessageSenderImpl: ProjectStage=UnitTest
11 03, 2015 1:21:10 午後 org.jboss.weld.environment.se.WeldContainer shutdown
INFO: WELD-ENV-002001: Weld SE container STATIC_INSTANCE shut down

次に、ステージをProductionへ変更すると本番用のMessageSenderがインジェクトされているのがわかります。

$ mvn clean install exec:java -Dexec.mainClass=”org.tanoseam.examples.ProjectStageExample” -Dorg.apache.deltaspike.ProjectStage=Production

11 03, 2015 1:31:31 午後 org.jboss.weld.environment.se.WeldContainer initialize
INFO: WELD-ENV-002003: Weld SE container STATIC_INSTANCE initialized
MessageSenderImpl: ProjectStage=Production
11 03, 2015 1:31:31 午後 org.jboss.weld.environment.se.WeldContainer shutdown
INFO: WELD-ENV-002001: Weld SE container STATIC_INSTANCE shut down

CdiTestRunnerを使ったテストでのProjectStageの指定

最後に、JUnitのテストプログラム内でProjetStageを指定する方法について書きます。JUnit のテストでは、これまでの説明と反するように聞こえるかもしれませんが、実行時に指定されたステージによって結果が変わるのでは正しいテストを実行することができません。そこで、@TestControlというアノテーションを指定することで、テストの実行で想定されるステージをテストごとに指定します(@TestControlを指定しない場合のステージのデフォルトはUnitTestです)。以下の例はUnitTestのステージを指定したときのサンプルになります。

package org.tanoseam.examples;

import javax.inject.Inject;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.apache.deltaspike.core.api.projectstage.ProjectStage;
import org.apache.deltaspike.testcontrol.api.TestControl;
import org.apache.deltaspike.testcontrol.api.junit.CdiTestRunner;

@RunWith(CdiTestRunner.class)
@TestControl(projectStage = ProjectStage.UnitTest.class)
public class ProjectStageTest {

	@Inject
	ProjectStage projectStage;
	
	@Inject
    MessageSender messageSender;

	@Test
	public void testSend() {
		messageSender.send("ProjectStage=" + projectStage);
    }
}

今度はCdiTestRunnerを使っているのでWeldの起動・停止のコードを書く必要がなく、 mvn test だけで実行できます。
全体的にテストコードがすっきりしましたね。

1 03, 2015 7:34:41 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testStarted
情報: [run] org.tanoseam.examples.ProjectStageTest#testSend
11 03, 2015 7:34:41 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testStarted
情報: [run] org.tanoseam.examples.ProjectStageTest#testSend
TestMessageSenderImpl: ProjectStage=UnitTest
11 03, 2015 7:34:41 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testFinished
情報: [finished] org.tanoseam.examples.ProjectStageTest#testSend
11 03, 2015 7:34:41 午後 org.apache.deltaspike.testcontrol.api.junit.CdiTestSuiteRunner$LogRunListener testFinished
情報: [finished] org.tanoseam.examples.ProjectStageTest#testSend
広告

コメントを残す

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

WordPress.com ロゴ

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

Twitter 画像

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

Facebook の写真

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

Google+ フォト

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

%s と連携中

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