Challenge Engineer Life !

エンジニア人生を楽しみたい!仕事や趣味で学んだ技術的なことを書いていくブログです。

ArquillianでViewScopedのCDI管理BeanだけInjectできない

年明けから、いよいよJava EE 7で開発が始まりそうなので、冬休みを利用して色々準備したいなーと思ってます。

まずはテスト周辺を整備、と思って久しぶりにArquillianを触ってたのですが、いきなりハマってしまった(^^;

GlassFish 4系でのArquillian設定は以下サイトがバッチリでした。日本語ありがたや!


GlassFish 4でArquillianうごかす - kagamihogeの日記

で、上記サイトと同じようにPOJOなクラスを試すと無事動き、環境OKと。

しかし、簡単なJSF+CDIのサンプル作って試してた所…あれっ、動かない!

管理Beanは次のように超簡易なもの。

@Named(value = "sumBean")
@ViewScoped
public class SumBean implements Serializable{
    public int sum(int x, int y){
        return x + y;
    }
}

faceletsはなくても…だけど一応。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>足し算</title>
    </h:head>
    <h:body>
        <h:outputText value="#{sumBean.sum(1, 1)}" />
        <h:outputText value="#{sumBean.sum(2, 2)}" />
        <h:outputText value="#{sumBean.sum(3, 3)}" />
        <h:outputText value="#{sumBean.sum(4, 4)}" />
        <h:outputText value="#{sumBean.sum(5, 5)}" />
    </h:body>
</html>

で、テストコードはこんな形。

@RunWith(Arquillian.class)
public class SumBeanTest {
    
    @Deployment
    public static JavaArchive createDeployment(){
        return ShrinkWrap.create(JavaArchive.class)
                .addClass(SumBean.class)
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }
    
    @Inject
    SumBean mySum;
    
    @Test
    public void sumTest() {
        assertThat(mySum.sum(1, 1), is(2));
        assertThat(mySum.sum(2, 2), is(4));
        assertThat(mySum.sum(3, 3), is(6));
        assertThat(mySum.sum(4, 4), is(8));
        assertThat(mySum.sum(5, 5), is(10));
    }
}

エラーはWELD-001303なるもので、次のようなメッセージが。

WELD-001303: No active contexts for scope type javax.faces.view.ViewScoped
org.jboss.weld.context.ContextNotActiveException
at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:687)
at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:79)
at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:78)
at sample.jsfwitharquillian.SumBean$Proxy$_$$_WeldClientProxy.sum(Unknown Source)
at sample.jsfwitharquillian.SumBeanTest.sumTest(SumBeanTest.java:38)

  • 略 -

f:id:kikutaro777:20141231010527j:plain

ぐぐるとWildFlyだけど、次のが一番近い感じ。

Unable to test EE 7 ViewScoped beans like we us... | JBoss Developer

しかし不思議なのは、@ViewScopedを@RequestScopedや@SessionScopedに変えてみると動くこと。

f:id:kikutaro777:20141231010533j:plain

なんでViewScopedだけ駄目なん…(^^;;;うーむ謎だ。

一応コード置いておこ…

kikutaro/JsfWithArquillian · GitHub

追記
後日、@nekopさんに教えて頂きました。Arquillian Warpを使うとできるようです!

早速@emaggameさんが試されていました!

Warpは触ったことなかったので、私も近々やってみようと思います。

JJUG ナイトセミナ 「Java EE 特集」に参加しました~

今日のJJUGナイトセミナは「Java EE特集」でした。最終的な参加者は170名超えだったとのことで、あれ、結構Java EE盛り上がってきてる…?のかと思ったり(^^;
http://www.java-users.jp/?p=846

発表は2つだったのですが、どちらもかなり勉強になりました。まだまだ知らないことあるなぁと。

というわけで、資料が公開されることを期待しつつ、感想などを。
(資料は既に公開されてました!)

CDIをはじめよう @n_agetsuさん

CDIは金魚本に少ししか載ってなくて、自分も前回の開発ではネットを中心に、そして英語情報中心な感じで使ってました。

今日の話をもっと前に聞いていれば…(*´Д`)と思ったり。

そもそもDI(Dependency Injection)とは…的な話から丁寧に説明があって、改めてふむふむ、と。

インジェクションされる側の条件とか生成される側の条件とか、インジェクションできる対象が複数ある場合の挙動(デプロイ失敗)とか、こういう細かい所って、わりとなんとなく…な感じで使ってたなぁ、と今日の話を聞きながら反省(^^;

Java EE6と7での話やCDIEJBの機能比較・使い分けなどなど、この資料めっちゃ欲しいです!という感じです!是非
と書いてたら早速資料を上げて下さってました!!!

あと個人的にびっくりだったのは、Arqullianキーワードが意外とホットな感じだったことw
あれ、みんな普通に使ってる…!?みたいな。もっともっと日本語情報増えると嬉しい感じですね(^^)

しかし、前に聞いた@den2snさんのJSFや@n_agetsuさんが前に発表されていたjBatch、そして今回のCDIを聞いて、こうしたテーマで全体を綺麗に整理して色々説明できるってすごいなぁ、と改めて思いました。自分の場合は、開発で時間に追われる面もあって(言い訳…)、使う所だけを食ってる感じなので…。

CDIに関してはブログでも記述されてるとのことで、以下記事も要チェックです。
Java EE環境におけるCDIのデフォルト化

おっぴろげ JavaEE DevOps @nagaseyasuhitoさん

今日の会場でもあったグリーの永瀬さんからの話でした。

Java EE7も出て、Java EEいいね、いいね、と聞くけど、実績の話あまり聞かなくないですか?本当に使われてるんですか?的なスタートw

ちょうど今日関連する記事が出てましたね~と、永瀬さんが発表の中で自分のインタビュー記事を紹介して下さり、恐縮でした(^^;
(たまたまなのですが、少し前に自分のJava EE6開発に関してOracleさんにインタビューして頂いた記事の公開が今日でした→初めてのJava EE 6開発! 最初の壁をどう乗り越えた?──最新Java EE開発“虎の穴” 第1回)

そんな流れの中、グリーさんでの初Java EEの実績話、でした。

ってか、グリーさんがJava EE!?とかちょっと思いましたが、流れ的にはScalaで話が上がったのち、かくかくしかじかでJava EEになったとのこと。構成としては、JAX-RSCDIJPAがメインでJMSなども利用しているそうです。

コード例も豊富だったのでこちらも是非資料が欲しい…(>_<)と書いてたら、もうあがってました(^^;
発表資料もすごく綺麗で、コード例も使ってる技術の部分が色付けされてて、読み手に優しい感じでした。

聞いてて自分の勉強が足りないなーと思ったのはGlassFishのasadminコマンドやmavenのプラグインの使いこなし度(ってか他も色々なんですが)。そんなのあるのかーという発見が(^^;

あとはサービス止めずにGlassFishのデプロイモジュール差し替えとかは、まさに運用ノウハウな感じですごかったです。監視系も。この辺、今の自分達が弱すぎる面なので勉強していきたい。

Arqullianも利用されていたり、JenkinsによるCIや自動デプロイも行ってるとのことで、色々共通点あるなぁと思いつつ、ユニットテストとインテグレーションテストの間にソースいじられてコミットされたケースとかまで想定されていて全体的にレベルがすごい(^^;そこまで考えたことなかった。。

と、そんなこんなで、どちらもすごく勉強になりつつ、良い刺激になりました。

Arqullian Persistence ExtensionでDB含む検索処理のリグレッション試験を楽にする

現在のJava EE6開発では、単体試験にJUnit、もう少し複雑な試験(インテグレーションというのでしょうか)Backing BeanやEJBが絡むようなもの、では主に「Arqullian」を利用しています。

Arqullianに関しては日本語での情報もあり、かつ、有志の方がArqullianの公式サイトでも翻訳されており、スタートアップの敷居に関しては、英語しかない状況に比べると楽かと思います(^^)私もそれらに助けられて触ってみた人間です。

私がよく参考にさせて頂いたサイトは以下です。

ArqullianではさらにExtensionとして「Arqullian Persistence Extension」というものがあり、これを使うことで、BackingBean + EJB + DBまで含めた試験が可能となります。

例えば、現在、検索処理のテストでは以下のようなテストをしています。

f:id:kikutaro777:20130313234246j:plain

非常に嬉しいのは、テスト用のデータをXML(他にXLS,YAML,JSONも可能らしいです、私は使ってないですが)で定義して、アノテーションをちょっと入れるだけで

  • テスト実行時に動的に自動でレコードを挿入
  • テストが終わったら自動で消してくれる

ことができる点です(^^)

XMLは以下のような感じで

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <XxxTable XxxID="XXXXXX"
                    XxxName="hogehoge" 
                    … テーブルの属性繰り返し …
                    CreateUser="makimaki" 
                    UpdateUser="makimaki" 
                    CreateDate="2013-3-3 20:00:00.000" 
                    UpdateDate="2013-3-4 22:00:00.000" />
</dataset>

Arqullianのテストコード(抜粋)は以下のような感じです。

@Test
@UsingDataSet("上で定義したXMLのデータ.xml")
@Cleanup(phase= TestExecutionPhase.AFTER, strategy= CleanupStrategy.USED_ROWS_ONLY)
public void XxxマスタでXxxNameで前方一致の検索するテスト(){
    …テストコード…
}

例えば検索処理の場合には、この挿入レコードを引っかけるようなテストコードを書きます。

なので、本番さながら、というか、試験環境のデータベースを利用しながら、実行ができる優れものです。

これのおかげで、検索処理のリグレッション試験が本当に楽になって(あ、検索に限らずですが)、リファクタリングやクエリの書き換えも安心してできるようになっています。

過去プロジェクトでは、こうした自動テストもなく、毎回手動のリグレッションをしていました。
検索処理は、入力された条件の組合せだけでも結構なテスト量が必要で、全ケースを毎回手でやるのは限界がありました(^^;

環境設定でつまづくと時間を食ってしまったり、テストコードを書くコストがどうしても増えてしまうのですが、運用保守系のシステムは息が長いし、リリース後の改造も多いので、こうした仕組みを早い段階で入れておくのが(自分自身の過去の経験的にも)いいなぁ、と個人的には思います。

環境設定についてはどこかのタイミングでまとめたいなと思っています。

Arquillian Persistence ExtensionでMicrosoft SQL Server 2008 R2のDBを使う場合

本開発に入る前に試していたArquillian Persistence Extensionは主にApache Derby(JavaDB)を利用していました。
※Arquillian Persistence Extensionとは何か、に関しては過去エントリ参照→「ArquillianPersistenceExtensionを触ってみる

で、本格開発に入って、Microsoft SQL Server 2008 R2を使うことになったのですが、組み込んでみた所…動かないっ。

原因は簡単で、Embedded GlassFishを利用しているのですが、MS SQLのドライバとなるsqljdbc4.jarの参照がないことのようです。

ShrinkWrapでdomainのlib配下にaddする…?とか色々考えてたのですが、ググった所、Arquillianのcontributorの方が以下pomを書いていて答えがありました。

mavenのローカルリポジトリにsqljdbc4.jarを登録すれば良いとのこと。

mvn install:install-file -Dfile=D:\Driver\JDBC\sqljdbc_4.0\jpn\sqljdbc4.jar -DgroupId=com.microsoft.sqlserver -DartifactId=sqljdbc4 -Dversion=4.0 -Dpackaging=jar -DgeneratePom=true -DcreateChecksum=true

試した所、無事動きました!

が、今朝Jenkinsからビルドエラーメールが来ていて、確認した所、構成管理サーバのJenkinsナイトリービルドでこけてました。

当たり前なんですが、ローカルリポジトリを構成管理サーバがみれないわけで…。
最終的にはinternal repositoryにsqljdbc4.jarを登録して無事終了です。

Arquillianテストで自分のミスに気付かずハマってたこと

昨年作ったJava EE6のWebプロトタイプをベースにArquillianでインテグレーションテストを実際に作っています。2月から本格的に開発に入るとのことで、駆け込みで色々ちゃんと確認しないと;

で、今日は半日くらい悩んだことがあって…でも結果的には自分の悲しいミスだったので自分への戒めとして残しておきます(-_-;

Could not create stateless EJB!

前にEJBを使ったサンプルを書いてみて問題なく動いていたので、安心してプロトにも組み込んでいったのですが、あるEJBを以下のようにInjectして、テストを実行するとエラーとなりました。

package jp.co.hoge.hogehoge.bean;

import java.io.File;
import java.util.List;
import javax.inject.Inject;
import jp.co.hoge.hogehoge.business.OpportunityBusinessLogic;
import static org.hamcrest.CoreMatchers.*;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class OpportunityListBeanTest {
    
    public OpportunityListBeanTest() {
    }
    
    @Deployment
    public static Archive createDeployment(){
        return ShrinkWrap.create(WebArchive.class, "OpportunityListBeanTest.war").
                addPackage("jp.co.hoge.hogehoge.bean").                
                //~省略~ 必要なパッケージ分、記述
                addAsResource("META-INF/persistence.xml").
                addAsWebInfResource(new File("src/test/resources", "glassfish-web.xml")).
                addAsWebInfResource(new File("src/main/webapp", "WEB-INF/beans.xml"));
    }
    
    @BeforeClass
    public static void setUpClass() {
    }
    
    @AfterClass
    public static void tearDownClass() {
    }
    
    @Before
    public void setUp() {
    }
    
    @After
    public void tearDown() {
    }
    
    @Inject
    OpportunityBusinessLogic opportunityBusinessLogic;
    
    @Test
    public void 商談検索_商談名で一本引きするパターンテスト(){
        List selectList = opportunityBusinessLogic.searchOpportunityList("OpportunityName","商談6080", null, null, true, false, true, "makimaki");
        assertThat(selectList.size(), is(1));
    }
}

エラーは以下のもの。

javax.ejb.EJBException: javax.ejb.CreateException: Could not create stateless EJB

f:id:kikutaro777:20130125201543j:plain

あれ、なんでこのプロトだとうまくできなんだろ?と。何か色々試してやったけど駄目で…。

今思えば、ここでググってしまったのが間違いだったですが、ググった所、同じエラーで悩む色々な情報があり、もしやstatelessなEJBでは使えない?、いや、そんなはずはない、とか思いつつ、色々なサイトにいっては試して…という繰り返しをしていました。

で、一度原点に帰って、以前に作ったシンプルなEJBでのサンプルを引きずり出し、実行すると動きます。絶対にプロトの中になにかあるー、と思ってログをしっかりみると

f:id:kikutaro777:20130125200735j:plain

IdGenerator.javaという自分達で作ったソースファイルが!
最初にググってしまったことによる先入観と、長いStacktraceをちゃんと読んでいなかった自分に喝を入れねばなりません。

で、なぜここで落ちるか、さらに探ってみると、WEB-INF/classes配下にxmlファイルを定義しており、それを読む処理があって、このxmlがWebArchiveに入っていなかったというオチ…。

propertiesファイルなんかも気を付けないとハマりそうだなぁ、と思いました。
Arquillianはwarをデプロイして本格的な試験ができて良いですが、依存性の記述欠如やこうしたファイル群の記述欠如による失敗も多かったりして、慣れるまで注意が必要かなと。

修正後

classes配下にファイルを置くのも微妙な気がしなくもないのですが、こういった場合でも以下のようなコードでちゃんと食わせられました。

@Deployment
public static Archive createDeployment(){
    return ShrinkWrap.create(WebArchive.class, "OpportunityListBeanTest.war").
            addPackage("jp.co.hoge.hogehoge.bean").                
            //~省略~ 必要なパッケージ分、記述
            addAsResource("META-INF/persistence.xml").
            addAsDirectory("WEB-INF/classes").
            addAsWebInfResource(new File("src/test/resources", "glassfish-web.xml")).
            addAsWebInfResource(new File("src/main/webapp", "WEB-INF/beans.xml")).
            addAsResource(new File("src/main/webapp/WEB-INF/classes" ,"idgenerator.xml"));
}

補足
と書いていたのですが、なぜか動かなくなりました…(-_-;これ書いたときは確認してちゃんとxmlが読まれるのを確認したのですが…何か変に見逃してしまったのか…。
以下標準出力にもあるようにファイルはArchiveにいるはずなんですが。

Archiveの中身の簡単な確認方法

以下はArquillianのGetting Startedにも載っているのですが、ShrinkWrapのcreateを使う所で以下のように記述しておけば、簡単にArchiveの中身が確認できます。多くのサンプルは直接returnする形式が多いですが、意外と便利なんじゃ…と思ったり(^^;

@Deployment
public static Archive createDeployment(){
    Archive archive = ShrinkWrap.create(WebArchive.class, "OpportunityListBeanTest.war").
            addPackage("jp.co.hoge.hogehoge.bean").                
            //~省略~ 必要なパッケージ分、記述
            addAsResource("META-INF/persistence.xml").
            addAsDirectory("WEB-INF/classes").
            addAsWebInfResource(new File("src/test/resources", "glassfish-web.xml")).
            addAsWebInfResource(new File("src/main/webapp", "WEB-INF/beans.xml")).
            addAsResource(new File("src/main/webapp/WEB-INF/classes" ,"idgenerator.xml"));
        //Archiveの中身を標準出力
        System.out.println(archive.toString(true));
        
        return archive;
    }
}

以下のような出力がされます。

OpportunityListBeanTest.war:
/WEB-INF/
/WEB-INF/beans.xml
/WEB-INF/glassfish-web.xml
/WEB-INF/classes/
/WEB-INF/classes/jp/
/WEB-INF/classes/jp/co/
/WEB-INF/classes/jp/co/hoge/
/WEB-INF/classes/jp/co/hoge/hogehoge/
/WEB-INF/classes/jp/co/hoge/hogehoge/encryption/
/WEB-INF/classes/jp/co/hoge/hogehoge/encryption/Sha256.class
/WEB-INF/classes/jp/co/hoge/hogehoge/encryption/Md5.class
/WEB-INF/classes/jp/co/hoge/hogehoge/encryption/IMessageDigest.class
/WEB-INF/classes/jp/co/hoge/hogehoge/id/
/WEB-INF/classes/jp/co/hoge/hogehoge/id/RootId.class
/WEB-INF/classes/jp/co/hoge/hogehoge/id/AbstractRootId.class
/WEB-INF/classes/jp/co/hoge/hogehoge/id/AbstractIdGenerator.class
/WEB-INF/classes/jp/co/hoge/hogehoge/id/IdGenerator.class
~中略~
/WEB-INF/classes/idgenerator.xml
/WEB-INF/classes/META-INF/
/WEB-INF/classes/META-INF/persistence.xml

これがあれば、ちゃんとwarの中身を確認できるので安全かなと。
ふぅ、今日はテストコードのデバッグに時間を食ってしまった感じです…(-_-;

Arquillian Persistence Extensionでもう少し日本語を活用してみる

NetBeansMavenJUnit&Arquillian(with Embedded GlassFish)でテストコードの日本語メソッドが利用可能なことがわかったので、次期案件ではこれを標準にしようと思います(開発は日本人のみだし、海外に出すことはなさそうなので)。

今まで上記組合せで実験していて、所々文字化けとかで上手くいかない部分がありましたが、対応は以下でできるかと思います。また別の問題が今後でないことを祈りますが(^^;

で、日本語テストメソッド書いてたら、テスト関連だったら日本語を活用したい欲が大きくなって、先日やったArquillian Persistence Extension使ったDB比較の元データ部分も日本語にできるんじゃ…と思いやってみました。

DB投入元データやDB期待値データのファイルも増えていくとメンテが手間になりそうですが、ファイルも日本語名でいけるなら少しはマシかな、と。

こんな感じです。

英語ベース

テストコード

@Test
@UsingDataSet("opportunity_data/opportunity_total_count_test_import_data.xml")
public void opportunityTotalCountTest(){
    assertThat(oppFacade.count(), is(2));
}

テスト実行結果

f:id:kikutaro777:20130112162606j:plain

NetBenasのプロジェクト構成

f:id:kikutaro777:20130112162550j:plain

日本語ベース

テストコード

@Test
@UsingDataSet("商談関連のテストデータ/商談件数確認テストの元データ.xml")
public void 商談件数の確認テスト(){
    assertThat(oppFacade.count(), is(2));
}

テスト実行結果

f:id:kikutaro777:20130112162634j:plain

NetBenasのプロジェクト構成

f:id:kikutaro777:20130112162656j:plain

@UsingDataSetのファイル名の所でも普通に日本語使えました。やったー!

結果ウィンドウにしろ、プロジェクト構成の所にしろ、個人的には色々見やすいな、と(^^;今の時代、エンジニアに英語は必須だぞ!という声も聞こえますが、メソッド名もファイル名、英語で名づけるのって結構難しくて無駄に時間かかっちゃったりもするので、TPOに合わせさえすればこれはありだな、と思います。

ArquillianのテストでFacesContext.getCurrentInstance()がnullなのでモックを作る

昨年作ったJava EE6のプロトタイプにArquillianを使ったテストを入れてどんなことまでできるか確認してます。
単体レベルの試験も簡単にできるし、DB使った試験も簡単だし、かなり良い感じです。

とりあえず今のところ、躓いたのはFacesContext周辺。

画面サイドはSeleniumを利用してブラウザレベルでグルグルテストするので、Arquillianで画面サイドまではテストするつもりはないのですが、ログインのbacking beanの中で、ログ出力のためにクライアントのIPアドレスを取得するような以下プログラムがあって

private String getRemoteIpAddr(){
    ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
    HttpServletRequest request = (HttpServletRequest)externalContext.getRequest();
    return request.getRemoteAddr();
}

テストすると、FacesContext.getCurrentInstance()がnullとなってしまって失敗してしまいます。この辺はJSFUnitを使う、という記事がありましたが、今の所そこまで気合い入ってなくて、ここをちょいと抜けてくれればいいんだけど…という思いがありました。

元のコードを変えて、そもそもFacesContext.getCurrentInstance()がnullの場合は、みたいな分岐エラー処理を入れてもいいかと思ったのですが…うーん、どうしよう、と。

悩んでいる中「FacesContext null test」とかでググってたら、そもそもArquillian関係なく、JUnitでbacking beanをテストするケースがあって、同じように困っている人が多々。

で、対策の1つにモックを使う方法がありました。なるほど。

参考にさせて頂いたのは「JSF:mocking FacesContext for unit tests」の記事です。

記事にあるContextMockerクラスをお借りして、以下コードをお試ししたら、ちゃんとテストできました!

モック作りにMockitoを使うので、まずはpom.xmlに記述

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>

ContextMockerクラスはテストパッケージ内に作成しました。

ログインのbacking beanのログイン処理をテストします
ログイン成功時の戻り値は画面遷移先のURLなので、それを比較します。

@Test
public void ログイン成功のテスト(){
    FacesContext context = ContextMocker.mockFacesContext();
    ExternalContext ext = mock(ExternalContext.class);
    HttpServletRequest request = mock(HttpServletRequest.class);
        
    when(context.getExternalContext()).thenReturn(ext);
    when(ext.getRequest()).thenReturn(request);
    when(request.getRemoteAddr()).thenReturn("10.255.255.1");

    loginBean.setUserId("horikita");
    loginBean.setPassword("makimaki");
        
    assertThat(loginBean.login(), is("/opportunity/opportunityList.xhtml"));
}

テストの結果
f:id:kikutaro777:20130111172432j:plain
どーん。やったー!JSFUnitまで手を出すのはしんどいなぁと思っていたのですが、これなら簡単なので、良い感じです。すごいー。

Arquillian Persistence Extensionのオプションを幾つか確認してみた

ここ数日Arquillian Persitence Extensionのエントリが続いてますが、基本的には
ドキュメントのことを試して書いてるだけです…(^^;

今日はいくつかのオプションを試してみたメモです。

テーブル比較にて除外したいカラムがある場合

昨日書いた「Arquillian Persistence Extensionでテーブルの比較テストに挑戦」でテーブルの元データと、プログラムで処理された後に、こうなるだろう、という期待データとを比較することをやりました。

昨日のサンプルだと超簡易なテーブルなのでいいのですが、通常業務系のDBだと登録日時や更新日時があったりするので期待データを書くが難しかったりします。

保存された時間の分・秒書くなんてナンセンスすぎるし…。で、そんなときはShouldMatchDataSetアノテーションの属性にある「excludeColumns」を使えばいいようです。

試しに昨日のプログラムを以下のように変えて

@Test
@UsingDataSet("dataset.xml")
@ShouldMatchDataSet(value = "expected_dataset.xml", excludeColumns={"name"} ) //←nameカラム比較から除外してみる
public void データベースの更新テスト(){
    MyMeibo myMeibo = meibo.find(0002);
    assertThat(myMeibo.getName(), is("makimaki")); //取得できるのはmakimakiだよね?
    
    //ID 0002のレコードの名前をmakimakiからmakiへ変更するよー
    myMeibo.setName("maki");

    //更新してみるねー
    meibo.edit(meibo);
}

わざと昨日の失敗パターンデータでテストしてみました。

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <mymeibo id = "0001" name = "horikita" />
    <mymeibo id = "0002" name = "makidayo" />
</dataset>

上記テストプログラム的には「IDが0002のデータのnameをmaki」にしたので、期待データが「makidayo」となっているとテスト失敗するのですが、「name」カラムの比較を除外しているので、成功します。おー。

テーブルの元データはそのままにしたい場合

Arquillianを初めて触ったときに書いた「Arquillian Persistence Extensionを触ってみる」は実行すると、元々登録されていたデータに加えてdataset.xmlのデータをinsertして、その後、テーブルデータが削除されます。

ここら辺の挙動はテスト内容によって変えたい場合があると思います。例えば元のデータを消さずにxmlファイルで追加したデータだけを削除したい場合は@Cleanupアノテーションを利用して以下のように書けました。

@Test
@UsingDataSet("dataset.xml")
@Cleanup(phase= TestExecutionPhase.AFTER, strategy= CleanupStrategy.USED_ROWS_ONLY) //テスト後、元データは残して使ったデータだけ消してね
@ShouldMatchDataSet("expected_dataset.xml")
public void データベースの更新テスト(){
    MyMeibo myMeibo = meibo.find(0002);
    assertThat(myMeibo.getName(), is("makimaki")); //取得できるのはmakimakiだよね?
    
    //ID 0002のレコードの名前をmakimakiからmakiへ変更するよー
    myMeibo.setName("maki");

    //更新してみるねー
    meibo.edit(meibo);
}

言葉で書くとわかりにくいですね(^^;
以下のような流れとなりました。

テーブルに元々入ってるデータ

ID NAME
0000 kikutaro

テストで追加するデータ(@UsingDataSetアノテーションで入れるデータ)

ID NAME
0001 horikita
0002 makimaki

テスト実行時(元データに@UsingDataSetのデータが追加された)

ID NAME
0000 kikutaro
0001 horikita
0002 makimaki

テスト完了後

ID NAME
0000 kikutaro

うーむ、すごい。アノテーションだけでこういう設定ができるのが楽です。

テスト実行前にテーブルの元データを消して欲しい場合

元々のテーブル状態を気にせずに、テスト実行時には必ず事前に消しちゃってよ、みたいな場合は以下のように書けます。

@Test
@UsingDataSet("dataset.xml")
@Cleanup(phase= TestExecutionPhase.BEFORE,strategy= CleanupStrategy.STRICT) //テスト前に全部消しておく
@ShouldMatchDataSet("expected_dataset.xml")
public void データベースの更新テスト(){
    MyMeibo myMeibo = meibo.find(0002);
    assertThat(myMeibo.getName(), is("makimaki")); //取得できるのはmakimakiだよね?
    
    //ID 0002のレコードの名前をmakimakiからmakiへ変更するよー
    myMeibo.setName("maki");

    //更新してみるねー
    meibo.edit(meibo);
}

ちなみにさきほどと同じように書くと

テーブルに元々入ってるデータ

ID NAME
0000 kikutaro

テストで追加するデータ(@UsingDataSetアノテーションで入れるデータ)

ID NAME
0001 horikita
0002 makimaki

テスト実行時(ID=0000のkikutaroが消されてからinsertされる)

ID NAME
0001 horikita
0002 makimaki

テスト完了後
同上。

最後にデータが残る挙動となりますので、少しだけ注意が必要ですかね。
このあたりのCleanup設定についてはドキュメントで書いてあるので、実際に試しながらやるのが一番ですね。クエリとかも書けるみたいですが、とりあえずこれだけでも十分なような…。

JSFのFacesContext.getCurrentInstance()がnull

Arquillianを色々試していて、今日知ったのですが、テスト時にJSF周辺は
気を付ける必要があるということ。
自分がハマったのは、ログ出力のためにIPアドレスを取得する部分でFacesContext.getCurrentInstance()から色々引きずり出しているのですが、FacesContext.getCurrentInstance()自体がテスト時にnullとなることでした。

Arquillianテストコードのアーカイブする部分でfaces-config.xmlを入れていないからかな、と思ってやったのですが駄目でした orz

ググっていたら

などあって、そういうもののようです。
JSFUnitか…また僕にとっては新しいモノが出てきました(^^;頭が追い付かない。

Arquillian Persistence Extensionでテーブルの比較テストに挑戦

1/6のエントリ「Arquillian Persistence Extensionを触ってみる」の続きで、ドキュメントにもあるShouldMatchDataSetアノテーションを使ってみました。
これを利用することで簡単にテーブルデータの比較テストができますようです。

テスト内容

dataset.xmlで定義した名簿テーブルからIDが0002のエンティティを取得して
名前を変更して保存します。その変えた内容をexpected_dataset.xmlに定義して
正しく更新されたかどうかのテーブル比較テストを行います。

元データ

dataset.xmlとして以下を定義しました。

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <mymeibo id = "0001" name = "horikita" />
    <mymeibo id = "0002" name = "makimaki" />
</dataset>

期待データ

プログラムが正しく動けば、こうなるはず、というデータをexpected_dataset.xmlとして定義しました。2レコード目のname属性を「maki」とします。

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <mymeibo id = "0001" name = "horikita" />
    <mymeibo id = "0002" name = "maki" />
</dataset>

テストコード

以下のような感じで実行しました。

@Test
@UsingDataSet("dataset.xml")
@ShouldMatchDataSet("expected_dataset.xml")
public void データベースの更新テスト(){
    MyMeibo myMeibo = meibo.find(0002);
    assertThat(myMeibo.getName(), is("makimaki")); //取得できるのはmakimakiだよね?
    
    //ID 0002のレコードの名前をmakimakiからmakiへ変更するよー
    myMeibo.setName("maki");

    //更新してみるねー
    meibo.edit(meibo);
}

通常はテストコード内でデータ更新のコードとか書くのではなく、テストされる側のメソッド内で書かれると思いますが、今回は@ShouldMatchDataSetの挙動確認サンプルということでご了承下さい。。

実行 成功パターン

NetBeansでは、メソッドを右クリックすると「フォーカスしたテスト・メソッドの実行」というメニューがあるので、それを選択します。
f:id:kikutaro777:20130109195905j:plain
おおー。あっさり成功。

実行 失敗パターン

あまりに簡単に動いたので、「これホントに比較してくれてるのか…?」と疑ってしまいました(^^;
で、わざと期待データを変えて(makiをmakidayoとプログラム結果では合わないものを記述)実行すると…。

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <mymeibo id = "0001" name = "horikita" />
    <mymeibo id = "0002" name = "makidayo" />
</dataset>

失敗!
f:id:kikutaro777:20130109195914j:plain
すごい!失敗の理由もちゃんと記述されてるし。熱いです。

感想

DB関連はちょっとしたミスでバグが出やすいかと思います。普段コードを書いて、実際に走らせて、DBを見に行って、目視で「うむ」と確認して進みますが、リグレッションテストも毎回目視というのは正直しんどくて、改造が頻繁にあったりするシステムだと泣きます…というか今まで泣いてました(;_;)

でもこんな簡単にDB含めてテストできるなら、絶対やるべきだなと痛感。
JUnitDbUnitを組合わせても同じことができるかと思いますが、ArquillianはJava EEと相性が抜群、というか、warを実際にデプロイして動かしてる辺り、なんともいえない安心感があったり。。。素人感覚ですがテストコードが書きやすい気がします(^^;

とりあえず、次期案件で実際に利用してみよう。

Arquillianで日本語メソッドのテストが実行できなかった問題の回避法

昨日書いたエントリ「Arquillianで日本語メソッドのテストが実行できない…
が一応解決?回避?できたので書いておきます。

実行環境

Arquillianのテスト作成手順は自分向けにも別途まとめたいのですが、とりあえず今お試ししている環境をざっくりと。

NetBeans7.2.1
Mavenで作ったJava EE6のwebプロジェクト
EclipseLink 2.3.2
組み込み版のApache Derby 10.9.1.0 (DBテストが楽なため)
JUnit 4.11
Arquillian 色々(junit-containerやglassfish-embedded-3.1など)

回避法

Arquillianの問題ではなく、試験で使っている組み込みGlassFishに設定が足りなくて動かなかったっぽいです。
普段GlassFishを利用する際、文字化けを防ぐためにglassfis-web.xml

<parameter-encoding default-charset="UTF-8"/>

という定義を含めていますが、Arquillianでwarを生成するコードの所で、これ入れてないよなぁ、とふと思って入れてみたら、また別の文字化けしました(^^;汗
でもこれで変わるなら、と試しにUTF-8windows-31jにしたら動きました…なぜ;

一応動いたから良しとしたのですが、この設定はテスト時だけにしたいので、とりあえず今のお試しでは、元々WEB-INF配下に置いてあるglassfis-web.xml
以下フォルダにコピーして
f:id:kikutaro777:20130108105924j:plain

中身のエンコード部分をUTF-8からwindows-31jに書き換えました。

<parameter-encoding default-charset="windows-31j"/>

で、Arquillianのテストコードでは、war作る所で1行追加します。

@Deployment
public static Archive createDeployment(){
    return ShrinkWrap.create(WebArchive.class, "OpportunityListBeanTest.war").
        addPackage(LogExceptionInterceptor.class.getPackage()).
        // ...
        // 必要なパッケージを繰り返し加える
        // ...
        addAsResource("META-INF/persistence.xml").
        addAsWebInfResource(new File("src/test/resources", "glassfish-web.xml")). //これを追加
        addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
    }
}

テスト実行すると、昨日エラーだった日本語メソッドのテストが無事通りました!
f:id:kikutaro777:20130108105933j:plain

良かったー。これでチーム内にテストコードの日本語メソッドを普及できそうです。

にほんブログ村 IT技術ブログへ
にほんブログ村 にほんブログ村 IT技術ブログ Javaへ
にほんブログ村