Java EE 7やWildFlyが出てから、全然触っていなかったArquillianをそろそろまた使ってみようかということで。
前にもArquillianを使ったエントリを書いていますが、基本的にはその時と同じです。前提は、以下とします。
- 言語はScala
- ビルドツールはsbt
- テストはScalaTestを使用
- アプリケーションサーバはWildFly
- ShrinkWrapのMaven Resolverも使う
- Arquillian RemoteおよびManaged両方実施する
参考にしたリソースは、いろいろありますが代表的なところはこんなところです。
Arquillianではじめるコンテナを使ったテスト
http://backpaper0.github.io/ghosts/arquillian.html
pom-ee7web.xml
https://github.com/nekop/java-examples/blob/master/maven/pom-ee7web.xml
Arquillian Guides / Getting Started
http://arquillian.org/guides/getting_started/
Arquillian Guides / Creating Deployable Archives with ShrinkWrap
http://arquillian.org/guides/shrinkwrap_introduction/
ShrinkWrap
https://developer.jboss.org/wiki/ShrinkWrap
では、いってみましょう。
なお、順番はRemote、Managedの順で行いました。
sbtの設定
まずはビルドツールである、sbtの設定から。
build.sbt
name := "arquillian-getting-started" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.2" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-unchecked", "-deprecation", "-feature") incOptions := incOptions.value.withNameHashing(true) fork in Test := true // parallelExecution in Test := false jetty() artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) => //artifact.name + "." + artifact.extension "javaee7-web." + artifact.extension } makePomConfiguration := makePomConfiguration.value.copy(file = new File("pom.xml")) resolvers += "JBoss Public Maven Repository Group" at "https://repository.jboss.org/nexus/content/groups/public/" // Managedの時は必要 // envVars in Test += ("JBOSS_HOME", "../wildfly-8.1.0.Final") val jettyVersion = "9.2.2.v20140723" libraryDependencies ++= Seq( "org.eclipse.jetty" % "jetty-webapp" % jettyVersion % "container", "org.eclipse.jetty" % "jetty-plus" % jettyVersion % "container", "org.jboss.spec" % "jboss-javaee-web-7.0" % "1.0.1.Final" % "provided", // "org.wildfly" % "wildfly-arquillian-container-managed" % "8.1.0.Final" % "test", // Managed "org.wildfly" % "wildfly-arquillian-container-remote" % "8.1.0.Final" % "test", // Remote "org.jboss.arquillian.protocol" % "arquillian-protocol-servlet" % "1.1.5.Final" % "test", // Remote "org.jboss.arquillian.junit" % "arquillian-junit-container" % "1.1.5.Final" % "test", "org.jboss.shrinkwrap.resolver" % "shrinkwrap-resolver-depchain" % "2.2.0-alpha-2" % "test", "org.scalatest" %% "scalatest" % "2.2.1" % "test", "junit" % "junit" % "4.11" % "test", "org.jboss.resteasy" % "resteasy-client" % "3.0.8.Final" % "test", // テストでのHTTPリクエスト実行に使用 "org.apache.commons" % "commons-lang3" % "3.3.2" // ShrinkWrap Maven Resolverのサンプル的に使用 )
いきなり、Remote/Managedごちゃ混ぜですが。
ところで、このくらいのシンプルな依存関係の定義で、全然問題なく設定できました。以前はexclude書いたりいろいろしてましたが、sbt側で何か変わったのかなぁ…。
WARファイルとしても作成できるように、xsbt-web-pluginの設定も足しています。
project/plugins.sbt
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "1.0.0-M6")
で、build.sbtに戻りまして…sbtではbomは使えない(はず)なので、依存関係は普通に定義します。
Remoteを使う場合とManagedを使う場合の差は、以下になります。
// 環境変数の設定 // Managedの時は必要 // envVars in Test += ("JBOSS_HOME", "../wildfly-8.1.0.Final") // 依存関係の定義 // "org.wildfly" % "wildfly-arquillian-container-managed" % "8.1.0.Final" % "test", // Managed "org.wildfly" % "wildfly-arquillian-container-remote" % "8.1.0.Final" % "test", // Remote "org.jboss.arquillian.protocol" % "arquillian-protocol-servlet" % "1.1.5.Final" % "test", // Remote
上記は、Remoteを使う前提で書いているので、Managedの部分はコメントアウトしています。
forkはtrueにしなくてもいいと思いますが、デプロイするアーカイブを同じ名前にしたりするなら、テストの並列実行はオフにした方がよいと思います。
// parallelExecution in Test := false
今回は、並列実行を明示的に無効にしませんでしたが、特に問題なかったです。
ただ、最初はWARファイル名を指定していて、これが被っていたのでデプロイとアンデプロイが同時に行われ、エラーを見ることになりました…。
最後に、ShrinkWrapのMaven Resolverを使うためにpom.xmlを生成するのですが、この時「pom.xml」で作りたかったので、以下の設定を加えました。
makePomConfiguration := makePomConfiguration.value.copy(file = new File("pom.xml"))
これで、「makePom」で生成されるファイルがプロジェクト直下で、「pom.xml」という名前になります。
*この設定を入れなくても、pom.xmlの生成自体は可能です
テスト対象コードを書く
それでは、テスト対象のコードを書いてみましょう。
CDIを使って管理するServiceクラス。
src/main/scala/org/littlewings/javaee7/service/CalcService.scala
package org.littlewings.javaee7.service import javax.enterprise.context.RequestScoped @RequestScoped class CalcService { def add(p1: Int, p2: Int): Int = p1 + p2 def multiply(p1: Int, p2: Int): Int = p1 * p2 }
JAX-RS関連。先に作ったクラスを使用します。
src/main/scala/org/littlewings/javaee7/rest/HelloResource.scala
package org.littlewings.javaee7.rest import javax.inject.Inject import javax.ws.rs.{GET, Path, Produces, QueryParam} import javax.ws.rs.core.MediaType import org.apache.commons.lang3.StringUtils import org.littlewings.javaee7.service.CalcService @Path("hello") class HelloResource { @Inject private var calcService: CalcService = _ @GET @Path("index") @Produces(Array(MediaType.TEXT_PLAIN)) def index: String = StringUtils.repeat("Hello World ", 2).trim @GET @Path("add") @Produces(Array(MediaType.TEXT_PLAIN)) def add(@QueryParam("p1") p1: Int, @QueryParam("p2") p2: Int): String = calcService.add(p1, p2).toString }
Scalaを使っている時点で依存関係が追加されているのですが、ここでさらにムダにCommons Lang3を使っています。こういうのを使っても、ShrinkWrapでうまくデプロイされることを確認したかったので。
一応、JAX-RSの有効化も。
src/main/scala/org/littlewings/javaee7/rest/JaxrsApplication.scala package org.littlewings.javaee7.rest import javax.ws.rs.ApplicationPath import javax.ws.rs.core.Application @ApplicationPath("rest") class JaxrsApplication extends Application
arquillian.xmlを作る
Remote用に、arquillian.xmlを作成しておきます。
src/test/resources/arquillian.xml
<?xml version="1.0" encoding="UTF-8"?> <arquillian xmlns="http://jboss.org/schema/arquillian" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> <defaultProtocol type="Servlet 3.0"/> </arquillian>
まあ、これはなくてもよさそうな…。
テストコード
それでは、テストコードを書いていきます。
CDIでの管理対象クラスのテストコード
まずは、簡単なCDIの管理対象として定義したクラスのテストコードから。
import文から。
src/test/scala/org/littlewings/javaee7/service/CalcServiceTest.scala
package org.littlewings.javaee7.service import javax.inject.Inject import org.jboss.arquillian.container.test.api.Deployment import org.jboss.arquillian.junit.Arquillian import org.jboss.shrinkwrap.api.ShrinkWrap import org.jboss.shrinkwrap.api.spec.WebArchive import org.jboss.shrinkwrap.resolver.api.maven.Maven import org.scalatest.Matchers._ import org.scalatest.junit.JUnitSuite import org.junit.Test import org.junit.runner.RunWith
object定義。Scalaではstaticメソッドがないため、Arquillianのデプロイ対象のアーカイブは、ここで定義します。
object CalcServiceTest { @Deployment def createDeployment: WebArchive = ShrinkWrap .create(classOf[WebArchive]) .addPackages(true, classOf[CalcService].getPackage.getName) .addAsLibraries { Maven .resolver .loadPomFromFile("pom.xml") .importRuntimeDependencies .resolve("org.scalatest:scalatest_2.11:2.2.1") .withTransitivity .asFile: _* } }
…ありもしないpom.xmlをロードして依存関係を解決するコードが入っていますが、こちらは後でなんとかします。
あと、ScalaTestですが、こちらはデプロイ対象に含めないとうまく動きませんでした。これは、前に試した時もそうでした。で、文字列でハードコードしているのですが、かといってMaven ResolverのimportRuntimeAndTestDependenciesメソッドとか使ってしまうと、Arquillianまで含めて持っていってしまうので、ここは仕方なく割り切りました。
デプロイするWARファイルの名前は、ShrinkWrapに決めてもらいます。
ShrinkWrap .create(classOf[WebArchive])
テストの定義。
@RunWith(classOf[Arquillian]) class CalcServiceTest extends JUnitSuite { @Inject private var calcService: CalcService = _ @Test def add1Test(): Unit = calcService.add(1, 2) should be (3) @Test def add2Test(): Unit = calcService.add(2, 3) should be (5) @Test def add3Test(): Unit = calcService.add(1, 1) should be (3) @Test def multiply1Test(): Unit = calcService.multiply(1, 3) should be (3) @Test def multiply2Test(): Unit = calcService.multiply(3, 3) should be (9) }
ScalaTestを使っているとはいえ、Arquillianを使用する時は@RunWithアノテーションを使用したJUnitの形になるため、ここはJUnitSuiteを継承したサブクラスとして作成しています。
ちなみに、失敗するテストが混じっています。
JAX-RSのテストコード
続いて、JAX-RS側のテストコードを作成します。
import文からobject定義まで、一気にいきます。
src/test/scala/org/littlewings/javaee7/rest/HelloResourceTest.scala
package org.littlewings.javaee7.rest import java.net.URL import javax.ws.rs.ApplicationPath import javax.ws.rs.client.ClientBuilder import org.jboss.arquillian.container.test.api.{Deployment, RunAsClient} import org.jboss.arquillian.junit.Arquillian import org.jboss.arquillian.test.api.ArquillianResource import org.jboss.shrinkwrap.api.ShrinkWrap import org.jboss.shrinkwrap.api.spec.WebArchive import org.jboss.shrinkwrap.resolver.api.maven.Maven import org.littlewings.javaee7.service.CalcService import org.scalatest.Matchers._ import org.scalatest.junit.JUnitSuite import org.junit.Test import org.junit.runner.RunWith object HelloResourceTest { @Deployment def createDeployment: WebArchive = ShrinkWrap .create(classOf[WebArchive]) .addPackages(true, classOf[HelloResource].getPackage.getName) .addClass(classOf[CalcService]) .addAsLibraries { Maven .resolver .loadPomFromFile("pom.xml") .importRuntimeDependencies .resolve("org.scalatest:scalatest_2.11:2.2.1") .withTransitivity .asFile: _* } }
先ほどのCDIでの管理対象の時と同じと思いきや、1行増えています。
.addClass(classOf[CalcService])
これは、テスト対象のJAX-RSのリソースクラスが、このクラスに依存しているからですね…。一緒にデプロイしてあげないと、動きませんよ、と。
続いて、テストコード本体。
@RunWith(classOf[Arquillian]) @RunAsClient class HelloResourceTest extends JUnitSuite { private val resourcePrefix: String = classOf[JaxrsApplication] .getAnnotation(classOf[ApplicationPath]) .value @ArquillianResource private var deploymentUrl: URL = _ @Test def helloWorld1Test(): Unit = { val client = ClientBuilder.newBuilder.build try { val response = client .target(s"${deploymentUrl}${resourcePrefix}/hello/index") .request .get response.readEntity(classOf[String]) should be ("Hello World Hello World") response.close() } finally { client.close() } } @Test def helloWorld2Test(): Unit = { val client = ClientBuilder.newBuilder.build try { val response = client .target(s"${deploymentUrl}${resourcePrefix}/hello/index") .request .get response.readEntity(classOf[String]) should be ("Hello World") response.close() } finally { client.close() } } @Test def addTest(): Unit = { val client = ClientBuilder.newBuilder.build try { val response = client .target(s"${deploymentUrl}${resourcePrefix}/hello/add?p1=1&p2=3") .request .get response.readEntity(classOf[String]) should be ("4") response.close() } finally { client.close() } } }
JAX-RSクライアントでリクエストを投げて、その結果を確認しているコードですが、少し先ほどとは違うところがあります。
クラスの定義に、@RunAsClientアノテーションを追加して、クライアント側で動作することを示しています。
@RunWith(classOf[Arquillian]) @RunAsClient class HelloResourceTest extends JUnitSuite {
アクセス先のURLの基底部分は、JAX-RSのクラスから抜き出したり@ArquillianResourceアノテーションで取得します。
private val resourcePrefix: String = classOf[JaxrsApplication] .getAnnotation(classOf[ApplicationPath]) .value @ArquillianResource private var deploymentUrl: URL = _
これで
val response = client .target(s"${deploymentUrl}${resourcePrefix}/hello/index") .request .get
のような形でURLを組み立てることができます。
実行(Remote)
それでは、テストを実行してみましょう。
…の前に、pom.xmlを作成します。
> makePom [info] Wrote /xxxxx/arquillian-getting-started/pom.xml [success] Total time: 0 s, completed 2014/08/30 23:28:44
続いて、WildFlyを起動しておきます。
$ wildfly-8.1.0.Final/bin/standalone.sh
では、テスト実行。
> test
XNIO、JBoss Remotingの表示がちょこっと出て、テストが実行されます。
[info] HelloResourceTest: 8 30, 2014 11:29:43 午後 org.xnio.Xnio <clinit> INFO: XNIO version 3.2.2.Final 8 30, 2014 11:29:43 午後 org.xnio.nio.NioXnio <clinit> INFO: XNIO NIO Implementation Version 3.2.2.Final 8 30, 2014 11:29:43 午後 org.jboss.remoting3.EndpointImpl <clinit> INFO: JBoss Remoting version 4.0.3.Final [info] - helloWorld1Test [info] - helloWorld2Test *** FAILED *** [info] org.scalatest.exceptions.TestFailedException: "Hello World[ Hello World]" was not equal to "Hello World[]" (HelloResourceTest.scala:80) [info] - addTest [info] CalcServiceTest: [info] - add1Test [info] - add2Test [info] - add3Test *** FAILED *** [info] org.scalatest.exceptions.TestFailedException: 2 was not equal to 3 (CalcServiceTest.scala:49) [info] - multiply1Test [info] - multiply2Test [info] Run completed in 35 seconds, 576 milliseconds. [info] Total number of tests run: 8 [info] Suites: completed 2, aborted 0 [info] Tests: succeeded 6, failed 2, canceled 0, ignored 0, pending 0 [info] *** 2 TESTS FAILED *** [error] Failed tests: [error] org.littlewings.javaee7.service.CalcServiceTest [error] org.littlewings.javaee7.rest.HelloResourceTest [error] (test:test) sbt.TestsFailedException: Tests unsuccessful [error] Total time: 36 s, completed 2014/08/30 23:30:13
失敗するテストも入れていたので結果はNGですが、ちゃんとScalaTestの結果として表示されました。
OKそうですね!
実行(Managed)
WildFlyは、停止しておきます。
あとは、先ほどのbuild.sbtで環境変数JBOSS_HOMEを追加して
// Managedの時は必要 envVars in Test += ("JBOSS_HOME", "../wildfly-8.1.0.Final")
依存関係を、Managedの方を使うように設定します。
"org.wildfly" % "wildfly-arquillian-container-managed" % "8.1.0.Final" % "test", // Managed
以下の2つは不要です。
// "org.wildfly" % "wildfly-arquillian-container-remote" % "8.1.0.Final" % "test", // Remote // "org.jboss.arquillian.protocol" % "arquillian-protocol-servlet" % "1.1.5.Final" % "test", // Remote
あとは、
> test
でWildFlyが起動されテストが行われます。結果は端折ります〜。
一応、基本的なところはできたのではないでしょうか…。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/arquillian-getting-started
その他、確認したりしたソースなどはこちらです。
ResolveStage
https://github.com/shrinkwrap/resolver/blob/master/api/src/main/java/org/jboss/shrinkwrap/resolver/api/ResolveStage.java
PomEquippedResolveStageBase
https://github.com/shrinkwrap/resolver/blob/master/api-maven/src/main/java/org/jboss/shrinkwrap/resolver/api/maven/PomEquippedResolveStageBase.java
ScopeType
https://github.com/shrinkwrap/resolver/blob/master/api-maven/src/main/java/org/jboss/shrinkwrap/resolver/api/maven/ScopeType.java
ShrinkWrap Javadoc
http://docs.jboss.org/shrinkwrap/