Jenkins + Arquillianで簡単Continuous Integration Test

この記事は、Jenkins Advent Calendar 9日目の記事です。
Jenkins Advent Calendar jp 2011
前日はakiko_pusuさんの外部ジョブの監視も使ってみよう! でした。

Integration Testについて

Jenkinsでテストを行う際、実際にコンテナ(JavaEEコンテナなど)を使用してテストをすると、気を遣うことがいろいろありました。コンテナの管理、アーカイブの管理、Seleniumの管理などなど…。もちろんできないわけではありませんが、メンテする部分が多くて面倒です。そういった部分を一掃できると、より楽しいJenkinsライフがおくれると思いませんか?

Arquillianについて

Arquillianは、Javaの各種コンテナ用のテスティングフレームワークです。コンテナとは、特にServletEJBコンテナに限りません。以下の表にあるようなコンテナをサポートしています。また現在、活発に開発が行われており、遠からずver.1がリリースされるのではないかと思います。

Container Embedded*1 Remote*2 Managed*3
Apache OpenEJB 3.1    
Apache OpenWebBeans 1.0    
GlassFish 3.1  
JBoss AS 5.1  
JBoss AS 5    
JBoss AS 6.0
JBoss AS 7.0  
JBoss Reloaded 1.0    
Jetty 6.1    
Jetty 7.0    
Tomcat 6.0
WAS V7.0    
WAS V8.0  
Weld EE 1.1    
Weld SE 1.0    
Weld SE 1.1    
WLS 10.3    

実装

さっそく実装を見てみます。ここでは、JBoss Application Server 7(以下、AS7)とサンプルのKitchenSinkというアプリケーションを使います。これはシンプルなCRUDのアプリケーションです。

pom.xmlから関連のある部分を抜き出してみます。dependencyの設定。

<dependency>
  <groupId>org.jboss.arquillian.junit</groupId>
  <artifactId>arquillian-junit-container</artifactId>
  <version>1.0.0.CR4</version>
  <scope>test</scope>
</dependency>

そしてprofileにもArquillianの設定を行います。

<profile>
(snip)
   <!-- Run with: mvn clean test -Parq-jbossas-managed -->
   <id>arq-jbossas-managed</id>
   <dependencies>
      <dependency>
         <groupId>org.jboss.as</groupId>
         <artifactId>jboss-as-arquillian-container-managed</artifactId>
         <version>7.0.2.Final</version>
         <scope>test</scope>
      </dependency>
   </dependencies>
</profile>

次に、Arquillian独自の、arquillian.xmlを記述します。containerに、コンテナの設定を記述します。これはコンテナごとに異なりますが、AS7 に関しては、パスを指定します。が二つありますが、これについては後で説明します。

<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">

	<container qualifier="jboss-eclipse" default="true">
		<protocol type="jmx-as7">
			<property name="executionType">REMOTE</property>
		</protocol>
		<configuration>
			<property name="jbossHome">/opt/as/jboss-as-7.0.2.Final</property>
		</configuration>
	</container>
	<container qualifier="jboss-ci">
		<protocol type="jmx-as7">
			<property name="executionType">REMOTE</property>
		</protocol>
		<configuration>
			<property name="jbossHome">/opt/as/as702_jenkins/</property>
		</configuration>
	</container>
</arquillian>

最後にテストコードです。

@RunWith(Arquillian.class)
public class MemberRegistration1Test {
   @Deployment
   public static Archive<?> createTestArchive() {
      return ShrinkWrap.create(WebArchive.class, "test.war")
            .addClasses(Member.class, MemberRegistration.class, Resources.class)
            .addAsResource("META-INF/persistence.xml", "META-INF/persistence.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
   }

   @Inject
   MemberRegistration memberRegistration;

   @Inject
   Logger log;

   @Test
   public void testRegister() throws Exception {
      Member newMember = memberRegistration.getNewMember();
      newMember.setName("Jane Doe");
      newMember.setEmail("jane@mailinator.com");
      newMember.setPhoneNumber("2125551234");
      memberRegistration.register();
      
      assertNotNull(newMember.getId());
      log.info(newMember.getName() + " was persisted with id " + newMember.getId());
   }
}

@RunWithアノテーションJUnit実行時にArquillianのテストランナーを指定します。
@Deploymentアノテーションのついたメソッドで、テスト用のWARファイルを作成しています。テストに必要なファイルだけ指定すればよく、テスト用のWARファイルはかなり小さくなることが期待できます。*4
@Injectアノテーションは、Integration Testの利点ですね。Mockではなく実際のクラスがインジェクトされます。
テストメソッドの中は、普通のUnit Testとなんら変わらないように見えます。一つ違うのは、memberRegistration.register()の時点でデータがDBに永続化されていることです。


さて、今回はこのプロジェクトをgitを使って管理し、Jenkinsに登録します。gitのコミットフックでJenkinsのJobを実行しています。

Jenkins登場


Jobの設定についてはあまり特筆することはありません。一般的なGitリポジトリの設定とMavenの設定に、ちょっと付け足す程度です。



Pre Stepsについては後述します。
Buildに関しては、-P でArquillianを有効にしたプロファイルを指定しています。これで実行すると…


*5


ジョブの実行に成功しました…と書いても何が起きているのか分かりませんが、実際にはこれだけのコードで、以下の処理を行っています。

  1. テストランナーとは別のJVMでAS7を起動
  2. WARファイルをAS7へデプロイ*6
  3. AS7上でテスト実行
  4. テスト結果を収集
  5. アンデプロイ
  6. AS7を停止


わりと簡潔な記述で、ここまでできました。

なお、後述すると書いていたPre Stepsのコマンド

echo jboss-ci > src/test/resources/arquillian.launch


と、arquillian.xmlは関連があります。
ご覧になってわかるとおり、ArquillianはMavenから起動しています。つまり開発者のPCでも同じようにテストできるということです。
とはいえ、Jenkins環境と開発者のPC環境ではコンテナのパスなどが異なっているかもしれません。Arquillianでコンテナを切り替える方法はいくつかありますが、ここではarquillian.xmlにコンテナを複数記述し、同じディレクトリにそのidを書いたarquillian.launchというファイルを置くことで切り替えました。*7
また、このテストはとても簡単なのは確かですが、私のPCでは14秒でAS7の起動から終了を含めたジョブが完了しています。JBossに限らず、GlassfishWebLogicなども最近は起動が高速化しているので、Integration Testとはいえそれなりに短時間で実行できると思います。


余談として、Selenium Serverの起動とSeleniumのテスト実行まで行うDroneも紹介しようと思っていたのですが、試したところまだ不安定なので諦めました :-P


ということで、JenkinsとArquillianで簡単Continuous Integration Testの紹介でした。
明日のJenkins Advent Calendarは、id:masato-kaさんです。

関連リンク

*1:Embeddedはテストランナーと同じJVM上でArquillianがコンテナを起動して、テストを実行します

*2:Remoteは、テストランナーとは別のJVM上で起動しているコンテナに対してテストを実行します

*3:Managedは、テストランナーと異なるJVM上でArquillianがコンテナを起動して、Remote同様にテストを実行します

*4:多数のクラスが関連する場合にいちいち指定するのか?と思われるかもしれませんが、現在、それを解決するプロジェクトが進行中だそうです。

*5:おまけでPersona Pluginを使っています

*6:合わせてArquillian serviceをデプロイ

*7:必要であれば、JBossGlassfishWebLogicで同じテストをすることもできます。この場合はプロファイルを切り替えて実行することになるでしょう