初めてのトラックバック

はてなダイアリーでこのブロクを始めてから一月半程になるのですが、昨日id:nekopさんとid:wyukawaさんからトラバを送っていただきました。ブログ開設以来トラックバック欄が埋まったのは初めてのことです。最初は、自分の調べたことをメモとして残す手段として始めたのですが、やはり、多くの読者の方々に読んでいただき、記事を少しでも役に立てていただけるのであれば、本当にうれしいことだと感じました。
自分自身はブロクはもちろん、ツイッターなどを含めて、ネット上で情報発信するということに対して今まですごく臆病で、なんとなく避けてきたところがあります。しかし、やはり、達人プログラマーを目指すのであれば、プロジェクト内とか会社組織内に閉じてやっていたらだめで、ネット上で研究熱心なプログラマーや一流の技術者の方々と積極的に交流していくということが大切であるとあらためて感じました。私にとっては結構ハードルの高いところなのですが。(正直なところ、自分は典型的なアスペルガータイプなので人脈作りは大の苦手としております。自閉症スペクトラム指数自己診断で診断したら39点もありましたし。)
今年も残すところわずかなので、年内というのは厳しいので、来年中という目標にしたいと思いますが*1、今後は

  • ツイッタースマートフォン*2などの新しい情報ツールを使いこなす
  • OSSプロジェクトに参加して貢献する
  • ユーザーグループの活動に参加する
  • 自分の作ったライブラリーなどをOSSとして公開する

などにも挑戦してみたいと考えています。

*1:目標の期限を先延ばしにするのはよくないことですが。

*2:家庭内事業仕分けでなかなか嫁さんの承認が得られないのが問題ですが。使途が明確でないのに電話代が従来よりかかるというのはだめだそうです。

JBossの新しいMavenレポジトリー

既にとっくにご存知の方も多いかもしれませんが、JBoss用のMavenレポジトリーが2010年4月より新しいサイトに変更されているみたいです。
http://relation.to/Bloggers/JBossMavenRepositoryChanges
以前はhttp://repository.jboss.org/maven2/でしたが、新しいサイトはhttp://repository.jboss.org/nexus/content/groups/public-jboss/でアクセスできます。しばらくは古いサイトも継続して運用されると思いますが、pomのレポジトリー設定などは今後は新しいレポジトリーを指すように変更した方がよいかもしれません。

	<repository>
		<id>jboss</id>
		<name>JBoss Repository</name>
		<url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url>
	</repository>
	
	<!-- 古い設定	
	<repository>
		<id>jboss</id>
		<name>JBoss Repository</name>
		<url>http://repository.jboss.com/maven2</url>
	</repository>
	 -->

設定方法はMaven Getting Started - Users |JBoss Developerで説明されています。
新しいサイトでは先日紹介したMaven: The Definitive Guideを出しているSonatypeという会社(Maven職人の会社?)のNexusという製品を利用して、レポジトリーを運営しています。おそらくExt-JSだと思うのですが、以下のURLでリッチな画面上でアーティファクトを検索したりできて便利です。
https://repository.jboss.org/nexus/index.html

Spring TestでJBoss Embeddedサーバーを利用するための手順

Seamの場合最初からSeamTestというフレームワークが付属しており、Embedded JBossサーバーを使ってJBossの機能をTestNGのテストクラスから実行できます。同様にして、Spring Frameworkに付属しているSpring Testを利用して、Springベースのアプリケーションで自動化された結合試験を実行する際にEmbedded JBossサーバを利用するための手順を以下に示します。

前提知識 Spring Testについて

Spring TestについてはSpring参照マニュアルの9章を参照してください。
9. Testing
このフレームワークを利用するとSpringのDIコンテナーを使ったJUnitTestNGの単体試験を容易に作成することができます。ここではSpring Test自体の使い方については前提知識として説明しません。(とは言ってみたもののSpring Testの新しいバージョンに関する日本語のまとまった情報は今のところあまりないようですね。時間があったら別の機会に基本的な使い方についてまとめてみたいと思いますが。)

依存するライブラリーの設定

JBoss EmbeddedサーバーとSpring Testを利用するために最低限必要なライブラリーの依存関係を追加します。たとえば、Mavenを使った場合、以下をpomに追加します。(JBossの新しいMavenレポジトリー - 達人プログラマーを目指してで書いたようにjbossのレポジトリーを登録しておく必要があります。)

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>${spring.version}</version>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.jboss.embedded</groupId>
		<artifactId>jboss-embedded-all</artifactId>
		<version>beta3.SP12</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.jboss.embedded</groupId>
		<artifactId>jboss-embedded</artifactId>
		<version>beta3.SP12</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.jboss.embedded</groupId>
		<artifactId>thirdparty-all</artifactId>
		<version>beta3.SP12</version>
		<scope>test</scope>
	</dependency>
	<!--
	hibernate-all.jarにアノテーションやJPAを含めたHibernate関連のライブラリがすべて含まれる
	ため別途hibernateの依存関係を追加している場合はその依存関係をあらかじめ削除する必要がある
	-->
	<dependency>
		<groupId>org.jboss.embedded</groupId>
		<artifactId>hibernate-all</artifactId>
		<version>beta3.SP12</version>
		<scope>test</scope>
	</dependency>

JBoss Embeddedのダウンロードとインストール

jarファイルとしての依存関係とは別に、以下からJBoss Embeddedをダウンロードします。
http://repository.jboss.org/maven2/org/jboss/embedded/jboss-embedded/beta3.SP12/jboss-embedded-beta3.SP12-bin.zip
ダウンロードしたら、zipファイルを展開した中のbootstrapフォルダーの中身(bootstrapフォルダー自身は含まない)をsrc/test/resource配下にまるごとコピーします。以下のようになっているはずです。

VMオプションの指定(Java6の場合)

Java6の場合テスト実行時のVM起動時のオプションに以下を追加する必要があります。

-Dsun.lang.ClassLoader.allowArraySyntax=true

eclipseの場合、テストクラスごとに、毎回指定するのが面倒であれば、設定ダイアログを開きJava⇒インストール済みのJREを選択し、編集ボタンをクリックして表示される「JREの定義ダイアログ」でデフォルトのVM引数を編集できます。

JBoss起動を行うContextLoaderの実装クラスの作成

以上でEmbedded JBossサーバーを起動する準備は整ったのですが、問題はサーバーを起動するタイミングです。通常結合テストの場合SpringのコンテキストでJTAやJMSのサービスにアクセスさせる必要があるため、DIコンテナーの起動前にサーバーを起動する必要があります。したがって、通常の@Beforeメソッド内でJBossを起動するのでは手遅れです。この問題を解決するためには、以下のようなContextLoaderの実装クラスを作成します。

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.embedded.Bootstrap;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.support.GenericXmlContextLoader;

public class JBossBootstrappingXmlContextLoader extends GenericXmlContextLoader {

	@Override
	protected void prepareContext(GenericApplicationContext context) {
		super.prepareContext(context);
		
		try {
			if (!Bootstrap.getInstance().isStarted()) {
				Bootstrap bootstrap = Bootstrap.getInstance();
				bootstrap.bootstrap();
			}
		} catch (DeploymentException ex) {
			throw new RuntimeException("埋め込みJBossの起動に失敗しました。", ex);
		}
	}
}

あとは以下のように@ContextConfigurationのloader属性にて先ほど作成したクラスを指定してやります。

@ContextConfiguration(loader=JBossBootstrappingXmlContextLoader.class)
public class BatchJobLauncherTest extends AbstractJUnit4SpringContextTests {

これで、JTAやJMSなどJBossの機能を使った結合試験の自動化が実現できます。最初もっと大変かなと思いましたが、意外に簡単に設定できました。特に、EJBを使わないSpringアプリケーションであれば、わざわざデプロイも不要でDIコンテナーさえ起動すればよいので、思ったより軽量でした。

結合テスト専用プロジェクトの分離について

以上のようにEmbedded JBossとSpring Testを組み合わせることで、JBoss + Springのアプリケーションの自動化された結合試験を作成することができます。ただし、上記のように大量のjarファイルを依存させる必要もありますし、普通のJUnitの単体試験と比べると実行にも時間がかかります。よって、ベストプラクティスとしては結合試験専用のプロジェクトを別プロジェクトとして作成するのがよいと思います。

参考サイト

以下を参考にさせていただきました。
JBoss Embedded and Maven « Paul’s Weblog
http://myblog.shriharisc.com/2010/06/21/using-embedded-jboss-for-functional-flow-testing-for-ejb3/
(追記)
EclipseWTPの設定でJBossサーバーランタイムがライブラリーとしてクラスパスに追加されていると、以下の例外が発生するようです。この場合、プロジェクトのビルドの設定でJBossサーバーランタイムをクラスパスから除外する必要があります。

ERROR [org.jboss.kernel.plugins.dependency.AbstractKernelController] Error installing to Start: name=JMXKernel state=Create
java.lang.NoSuchMethodError: org.jboss.system.ServiceController.setKernel(Lorg/jboss/kernel/Kernel;)V
        at org.jboss.embedded.adapters.JMXKernel.start(JMXKernel.java:164)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:56)
        at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:110)
        at org.jboss.joinpoint.plugins.BasicMethodJoinPoint.dispatch(BasicMethodJoinPoint.java:66)
        at org.jboss.kernel.plugins.dependency.KernelControllerContextAction$JoinpointDispatchWrapper.execute(KernelControllerContextAction.java:214)
        at org.jboss.kernel.plugins.dependency.ExecutionWrapper.execute(ExecutionWrapper.java:45)
        at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchExecutionWrapper(KernelControllerContextAction.java:108)
        at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAction.java:69)
        at org.jboss.kernel.plugins.dependency.LifecycleAction.installActionInternal(LifecycleAction.java:221)
        at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.installAction(KernelControllerContextAction.java:135)
        at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.installAction(KernelControllerContextAction.java:46)
        at org.jboss.dependency.plugins.action.SimpleControllerContextAction.simpleInstallAction(SimpleControllerContextAction.java:62)
        at org.jboss.dependency.plugins.action.AccessControllerContextAction.install(AccessControllerContextAction.java:71)
        at org.jboss.dependency.plugins.AbstractControllerContextActions.install(AbstractControllerContextActions.java:51)
        at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:327)
        at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1309)
        at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:734)
        at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:862)
        at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:784)
        at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:574)
        at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:398)
        at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBean(AbstractKernelDeployer.java:309)
        at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBeans(AbstractKernelDeployer.java:279)
        at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deploy(AbstractKernelDeployer.java:130)
        at org.jboss.kernel.plugins.deployment.xml.BeanXMLDeployer.deploy(BeanXMLDeployer.java:96)
        at org.jboss.embedded.Bootstrap.deployBaseBootstrapUrl(Bootstrap.java:130)
        at org.jboss.embedded.Bootstrap.bootstrapURL(Bootstrap.java:142)
        at org.jboss.embedded.Bootstrap.bootstrap(Bootstrap.java:183)
        at org.jboss.embedded.Bootstrap.bootstrap(Bootstrap.java:195)
        at org.jboss.seam.mock.EmbeddedBootstrap.startAndDeployResources(EmbeddedBootstrap.java:11)
        at org.jboss.seam.mock.AbstractSeamTest.startJbossEmbeddedIfNecessary(AbstractSeamTest.java:1024)
        at org.jboss.seam.mock.AbstractSeamTest.startSeam(AbstractSeamTest.java:915)
        at org.jboss.seam.mock.SeamTest.startSeam(SeamTest.java:58)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at org.testng.internal.MethodHelper.invokeMethod(MethodHelper.java:580)
        at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:398)
        at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:145)
        at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:82)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:278)
        at org.testng.SuiteRunner.run(SuiteRunner.java:198)
        at org.testng.TestNG.createAndRunSuiteRunners(TestNG.java:823)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:790)
        at org.testng.TestNG.run(TestNG.java:708)
        at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:73)
        at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:124)