menu
書いてる野郎
orebike@gmail.com
ここでの記述は特に Service(データベース関係の処理)だけでなくもちろん、Entity や Form にも応用可能である。
Eclipseのヘルプ→新規ソフトウェアインスコから
http://eclipse.seasar.org/updates/3.3/
を追加して、S2Junit プラグインを入れる。
テスト対象の service が HogeService だったとして、そいつを・・・
操作
無いから作るか?と聞かれるので、作る。
そうすると
src/test/java/hoge/piyo/fuga/service
以下に HogeServiceTest.java
が自動的に作成されて、中身は・・・
package hoge.piyo.fuga.service; import static org.seasar.framework.unit.S2Assert.*; import org.junit.runner.RunWith; import org.seasar.framework.unit.Seasar2; @RunWith(Seasar2.class) public class HogeServiceTest { private HogeService hogeService; public void testPiyo() { System.out.println("hello"); fail("まだ実装されていません"); } }
・・・このようになる。これは自動的に作ってもらったが別に手で作っても構わない
テスト用の設定は
src/test/resources
以下に収める。
通常だったらs2junit4.diconのみがあるはず。
基本的に通常の
src/main/resources
の設定がそのまま使われる。
テスト独自の設定をしたいならば
src/test/resources
に設定を変えたいdiconファイルを
src/main/resources
からコピーして変えたい部分だけを書き換えるとそっちが優先的に読み込まれるっぽい
でテストが実行される。
結果がJUnitビューに失敗(赤バー)として表示されているだろう。OK!
特定のテストメソッド、testHogeメソッドの前処理をしたい場合は
beforeTestHoge
メソッドを実装する
特定のテストメソッド、testHoge メソッドの後処理をしたい場合は
afterTestHoge
メソッドを実装する
S2Junit4ではテストデータをエクセルで書いて取り込めるという便利なんだか不便なんだかよくわからん機能があるのでこいつを使ってデータを突っ込んでみる。※個人的にはエクセルファイルのような不透明で複雑な情報を持つファイルでテストデータを作るというのはどうかと思う。
テストファイル名は・・・
テストクラス名_テストメソッド名.xls
・・・となる。この設定はs2junit4.diconに書いてあるので変えることもできる。 このフォーマットではテストを大量に作っていくうちにパッケージ内がグチャグチャになるので、スラッシュ区切りにしてディレクトリにわけるとよい。
そしてこのファイルをHogeServiceTest.javaと同じパッケージに保存
のように記述する
そうするとテスト実行時にそのメソッドになったらエクセルに記述したデータをDBに突っ込んでくれる。 テスト終了後に自動的にロールバックされるので突っ込んだデータの後始末は考えなくてよい
実際の設定は↓のようになっている。ここで注目は2個書いてあるということ
<component class="org.seasar.framework.unit.impl.TestDataPreparerImpl"> <initMethod name="addTestDataXlsPath"> <arg>context.testClassShortName + "_" + context.testMethodName + ".xls"</arg> </initMethod> <initMethod name="addTestDataXlsPath"> <arg>context.testClassShortName + ".xls"</arg> </initMethod> </component>
ここでテストクラス名.xlsとすれば、テストクラス全体で読み込んでくれるということ。 設定としては先勝ちっぽいのでゆるい設定ほど後に書いていけばいいと思う
ぜんぜん違う名前のxlsファイルとかぜんぜん違うテストケース間で同じテストデータが使いたい場合は自動的なローディングはやめてDataAccessorを使ってテストメソッドごとに個別に設定できるみたい。
public class PiyoTest{ private DataAccessor accessor; public void testFuga1(){ accessor.readXlsAllReplaceDb("HogeTest.xls"); } }
DataAccessor自体は書いておけばSeasar側が勝手に突っ込んでくれる。コレで任意のテストデータのエクセルファイルを任意のテストケースで使えるようになる。
テストクラスに
@EasyMock private HogePiyo hogePiyo; public void recordGetFuga throws Exception{ expect(hogePiyo.getFuga()).andReturn("ふがのもっく"); }
のように書くらしい
そうするとテスト対象クラスのメソッドがHogePiyoに依存してgetFugaを呼び出している部分がexpectで定義した内容に変わるらしい・・
対象のテストメソッドが内部でインスタンスをインジェクションされていてその内部状態を利用している場合。 テストクラスにも同様のプロパティを作ってインジェクションしてもらい、その内部状態をテストメソッドで書き換える。
こうすることで、テスト対象クラス内部のインスタンスの値を制御できる。
ま、そもそもそうういう設計にしないというのが筋。
開発の初期の段階だと単に実行して自分でDBを見て値検証したい時ってある。テストメソッドを単なるインジェクション+実行トリガとして使いたい場合。こういう時は自動ロールバックがウザイ場合があるので切りたい。
テストメソッドにアノテーションで
@TxBehavior(TxBehaviorType.NONE)
と書けば抑止できる
一時的にテストを止めておきたい場合に別にコメントアウトしてもいいわけだがアノテーションで1行書くと無視してくれる
@Ignore pubilc void testHoge(){ }
このように書く
@Test(expected=HogeException.class) pubilc void testHoge(){ piyo()//例外が発生する処理 fail("例外が発生しなかったのでエラー"); //発生するとこの行には来ない }
TestContext ctx; public void before(){ ctx.setPreparationType(PreparationType.ALL_REPLACE); }
テストクラスに↑のように書きこんでおけば、エクセルファイル等からのデータのツッコミ前にテーブルデータを全部消してくれる。 テスト後はロールバックするので元に戻る。
単にテーブルの既存データが邪魔ならば
private DataAccessor accessor; public void testHoge(){ accessor.deleteTable("hoge"); }
で消すことができる
S2Junit4の規約ではデータベースのテーブル名とエクセルのシート名が対応している。・・・のだが
MySQLの識別子の限界が64文字らしくエクセルシートの文字の限界が31文字なのだ。つまり対応できない状況があるのだ。
まず長い名前のテーブルのデータを適当な短い名前でシートを作る。
private DataAccessor accessor; public void testHoge(){ DataSet ds = accessor.readXls("HogeTest_testHoge_data.xls"); DataTable dt = ds.removeTable("hoge_piyo"); dt.setTableName("hoge_piyo_fuga_hoge_piyo_fuga_hoge_piyo_fuga"); ds.addTable(dt); accessor.writeDb(ds); }
という感じに
ドキュメント、他の事例も充実してないのでとにかくなんか変なことでトラブルことが多い。メーリングリストで質問しろといえばそれまでなんだがね・・・
↓のように認識エラーが出る。そのままMySQLにつっこんでくれたら入りそうだけど、一旦Java側で咀嚼するようでエラーがでる
java.text.ParseException: Unparseable date: "2010-06-24 15:46:38"
これは、日付カラムはエクセルファイル上でも日付型でないといけない。なので書式から日付型に変更したらうまくいくかも
たぶんテーブルのエンジンがMyISAMになっている。MyISAMはトランザクションを張れないので当然ロールバックもできない。
InnoDBみたいなトランザクション管理できるエンジンに変更したらうまくいくかも
エクセルのセルの書式が関係しているみたい。セルごと除去して、値を作りなおすとうまくいくかも。
Eclipse上でリフレッシュしないと反映されない場合アリ。 というかテストデータ自体は編集しているファイルではなく、(Eclipseが自動的に)targetにコピーされたものを見に行っているのでこのシンクロがずれると反映されない。
テスト対象のメソッドが新規でトランザクションを開始する指定がされている場合になるかも
詳しくは→SAStruts/トランザクション管理
テスト対象のメソッドが新規でトランザクションを開始する指定がされている場合になるかも
詳しくは→SAStruts/トランザクション管理
本物のエクセルでxlsファイルを作るとうまくいくかも。
LibreOffice3.4でxlsファイルを作ってやった場合失敗した。LibreOfficeで作ったファイルを本物のエクセルで開いた場合、表示や書式が若干おかしくなってしまう。日付カラムがユーザー定義になってしまったりする。
やっぱり正式じゃないとダメなのか?
テストを実行すると通常実行では出ないIllegalAutoBindingPropertyRuntimeExceptionが
app.diconで
<component name="piyoFuga" class="hoge.piyo.fuga.PiyoFuga" instance="singleton"/>
と書かかれている対象で問題がおきているみたいpiyoFuga自体はテストとまったく関係無いクラスなのだがsingletonだから初期化の時に1個作られてしまうっぽい
今のところテストの設定では記述しないようにしている。
外部とHTTPで通信をするテストもあると思う。めんどう。
ここでJettyというJava製のhttpサーバを使うと、起動、パラメータ、レスポンス設定、停止という処理をを全部Java上からコントロールできるようになる つまりテストコードでサーバ状態を再現できる!繰り返しテストができる!というもの
さっそくやってみる
Maven で一発でいれてください。
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.2.1.v20140609</version> <scope>test</scope> </dependency>
テストクラスの内部クラスとして Jetty 制御用のクラスを作成する。
public class DummyOutput extends AbstractHandler{ @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ System.out.println("target = " + target); response.setContentType("text/html"); response.setStatus(HttpServletResponse.SC_OK); baseRequest.setHandled(true); InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("hogehoge_test" + target); OutputStream out = response.getOutputStream(); byte[] buff = new byte[1024]; int len = 0; while((len = in.read(buff, 0, buff.length)) != -1){ out.write(buff, 0, len); } } }
target には、ドメインのルートからの値が入ってくる。http://localhost:1234/hogehoge
ならば /hogehoge
が入る。
なのでこれによりテスト時のURL違いによる挙動の変化を場合分けすればよい。
レスポンスを直接String で書くこともできるが、このへんはファイルで記述したほうが楽だろう
before の prefix をつけてテスト前にJettyを起動するメソッドを作成する。
private Server server; public void beforeTestHogehoge() throws Exception{ server = new Server(1234); server.setHandler(new DummyOutput()); server.start(); }
この記述で http://localhost:1234
で 起動した Jetty のサーバにアクセスできようになる。
Jetty のサンプルコードでよく start
の後に join
メソッドを呼び出しているものがある。
テストではデーモンのようにずっと待ち受けるわけではなく、テストが終わったら終了させたいので join は呼び出さない。
join を呼び出すとテストの実行スレッドが Jetty の終了を待ち続けるのでテストが進まなくなる。
終了時にも Jetty を呼び出したいのでメンバに格納する。
Jetty は期待するアウトプットのダミーを吐いてくれるので、それに対する挙動をテストすればよい。
after の prefix をつけてテスト後にJettyを終了するメソッドを作成する。
public void afterTestHogehoge() throws Exception{ server.stop(); }
stop
メソッドを呼び出すと終了する
単純に一致
assertThat(result, is("ほげ"));
前方一致
assertThat(result, startsWith("ほ"));
後方一致
assertThat(result, endsWith("げ"));
指定した文字列で始まって、指定した文字列で終わる
assertThat(result, allOf(startsWith("ほ"), endsWith("げ")));
テストコード内でこのように取得できる。
String path = this.getClass().getResource("").getPath(); // 状況にもよるがこのような値が取れる // "/home/hogehoge/eclipse-workspace/foo/target/test-classes/com/example/teeesuuutooo/"
Java はソースコードがそのまま動くのではなく、実行時にコンパイルされて動くという性質上、ソースコードと実行ディレクトリが別になっている。 コンパイル時にJavaソースはコンパイルされ、それ以外のファイルは対象のディレクトリに構造まるごとコピーされる。
Java の実行時のファイルパスはそのことを意識する必要がある。