UTFluteでいろいろ

UTFlute- 0.4.0 0.4.6 をリリースしました

大きな変更点としては、
inject()メソッドの仕様を実際のDIコンテナに近づけました。
今まではかなりアバウトだったのですが、
「DIができるか」のテストに向かなかったので、
少し厳しめにしました。

ちなみに、テストケースクラス自体はDIは変わりません。
テストケースのクラスはアバウトの方がいいと思うので。
inject()メソッドにぶち込んだBeanに対するDIの話です。

って、そもそもUTFluteってなに?

ずばり!
「自分でnewしたものにDIできる JUnit 拡張」
です。

DBFlute の Example のテストケースで使っています。
幾つかの業務の現場でも導入しています。

jfluteがあんまりプロモーションをしていない、
「知る人ぞ知るツール」です。
今後もそんな感じです、きっと。

でも、身の回りでわりと使ってくれる人が
ちょっと増えてきたので、
(少しだけ)しっかり書いてみようと^^
FooAction action = new FooAction(); // 自分でnewする!
inject(action); // FooActionのフィールドにいろいろDI

// 自分でnewしてるのでInterceptorは抑制できる
// DIはされているのでDBアクセスとかはできる
action.index();
FooAction自体をDIすると...
Interceptorが動いちゃってテストがしづらい。

でも、自分でnewすると...
DIされてないからほとんど動かない。
自分でセット、Mockをセットとか言っても量が多すぎるよぅ。

ということで、自分でnewしつつDIされたらいいなぁってね。
そういう発想から作られました。

そして、自分でnewするとうれしいこともうひとつ。

FooActionにMockしたい処理があったときに、
オーバーライドで簡単にMockできるというところ。
FooAction action = new FooAction() {
    @Override
    protected FooBean findStoredBean() {
        // 本当は Memcached から探す!
        // でも、JUnitTest環境ではMemcachedなんて用意してない...
        // オーバーライドで適当な値を戻してしまえー
        return new FooBean(...);
    }
};
inject(action); // DIすることには変わりない

// findStoredBean()だけMockで実行される
// その他は普通に実行される
action.index();
もちろん、世の中便利なMockツールはたくさんありますが、
Javaの文法を知っていれば使えるってのは、
それはそれで手軽だなぁと。

UnitTestって「いろいろ」と敷居が高いものです。
なのでまあ、こんな感じのアプローチもあっていいかなってね。

// 現場のテストコードはどこへ?
http://d.hatena.ne.jp/jflute/20120806/1344262853

さて、ここからは 0.4.0 から導入された
新機能を見ていきましょう!

markHere()

ここを通った!っていうマークを付けられます。
// ## Act ##
memberBhv.selectCursor(cb, entity -> {
    ... = entity.get...
    markHere("called"); // 呼ばれた印
});

// ## Assert ##
assertMarked("called"); // それをassert
まあ、よく boolean 宣言してtrueにしてassert、
とか、よく Set を宣言して add して...
みたいなことをやっていたのを気軽に実現できます。

markHere()の引数の文字列は任意のキー。
なので、一つのテストケースで複数同時に利用できます。
JUnitは素通りするとGreenになってしまいますから、
assertしたこと自体を保証するためにも使えるかと。
// ## Assert ##
for (Member member : memberList) {
    if (member.isMemberStatusCodeFormalized()) {
        assertTrue(member...);
        assertMarked("exists"); // assertされた...
    }
    ...
}
assertMarked("exists"); // ...ことをassert
また、
「markしておきながらassertし忘れたー(><」
ってとき、
テストの終了時に例外になって教えてくれます。
// ## Act ##
memberBhv.selectCursor(cb, entity -> {
    ... = entity.get...
    markHere("called"); // 呼ばれた印
});

// ## Assert ##
// assertし忘れちゃった(><
// すると tearDown() で AssertionFailed 発生

cannonball() & projectA()

(デフォルトで)10個のスレッドを、

「いっせーのーせっ!」

で同時に実行させてマルチスレッドのテストができます。
cannonball(car -> {
    // ...(なにか処理をする)
    car.goal(memberList); // 処理結果をここに入れる
}, new CannonballOption().expectSameResult()); // みんな同じ結果
同じ結果であることのassertや、
例外が発生することのassertなど、
CannonballOptionでいろいろと用意されています。
もちろん、自分で適宜 assert を入れちゃってOK。

とにかく、気軽にマルチスレッドの「よーいどん!」
ができるのはいざってときにとても助かります。

っていっても、
業務のUnitTestではあんまり役に立たないかな...!?
まあ、DBFluteではめっちゃ使っています。
スレッドセーフであることを検証する必要があるので。
ライブラリ的なクラスのテストに向いてるんじゃないかと。

複数のスレッドに「順番に」処理をさせていくこともできます。
実際に、DBFluteDBMSのLock待ちのテストで利用しています。
cannonball(car -> {
    // /- - - - - - - - - - - - - - - - - - - -
    // 1番さんに親テーブルを更新してもらう
    // - - - - - - - - - -/
    car.projectA(dragon ->  {
        Member member = new Member();
        member.setMemberId(memberId);
        member.setMemberName("lock1");
        memberBhv.updateNonstrict(member);
    }, 1); // ここでどのスレッドにやってもらうか指定

    // 1番さんが終わるまで2番さんは待ち、
    // 終わったらまたみんなで一斉にスタート

    // /- - - - - - - - - - - - - - - - - - - -
    // 2番さんに子テーブルを登録してもらう
    // - - - - - - - - - -/
    car.projectA(dragon ->
        // 2番さんが3秒ルールの時間切れになることをassert
        // (3秒以内に終わったら AssertionFailed の例外発生)
        dragon.expectOvertime();

        Purchase purchase = new Purchase();
        purchase.setMemberId(memberId);
        purchase.setProductId(1);
        purchase.setPurchaseDatetime(currentTimestamp());
        purchase.set...
        purchaseBhv.insert(purchase); // ここでロック待ち
    }, 2); // この番号をエントリーナンバーと呼ぶ

    // 2番さんが待ちになって3秒経ったら1番さんは先に行って、
    // 1番さんの rollback でロック解放、2番さんやっとinsert完了
    // (ちなみに、3秒ルールはデフォルト)
    // expectOvertime()で、時間切れになることが保証される

}, new CannonballOption().threadCount(2)); // ふたりだけ

policeStoryOf...()

いろいろなものを chase できます。
例えば、src/main/java配下のJavaクラスを走査して、
アノテーションとか宣言のチェックとか...
// src/main/java配下の全部のJavaクラスの、
// File と Class<?> が callback で扱える
policeStoryOfJavaClassChase((srcFile, clazz) -> {
    // もう後は好きなようにチェックして!
    ... = clazz.getMethods();
});
さっそく現場でやっているのが...

SeasarのスマートデプロイのクラスのDIって、
インスタンス変数の名前でDIするのですが、
名前が間違ってるとDIされません。
@Resource
protected MemberLogic memberFlute; // これだめ!
例えば、

「先頭小文字にしたLogicクラス名 = 変数名」

であることをassertするテストケースを書いてみました。
まあ、もちろん画面起動でも既に動かないですけど、
HotDeployだとその画面に行かないとエラーにならないし、
とにもかくにもリファクタリングしたときとかに、
一括でチェックできるってのは安心感あります。

というか、これはほんの入り口、
もっと色々な独自のチェックができるかなと。
CheckStyleとかではなかなか届かない、
業務的な習慣のチェックとかに活用できればと。

※JSPファイルとかも走査できるので、まあいろいろと...^^

さいごに

Google Guice 対応もしました。
あと、HttpServlet の Mock も導入しました。

Maven の artifactId に指定できるものは:

o utflute-seasar ->  Seasarを使ってるなら
o utflute-spring -> SpringでWeb要らないなら
o utflute-guice -> GuiceでWeb要らないなら
o utflute-spring-web -> SpringでWebが要るなら
o utflute-guice-web ->: GuiceでWebが要るなら
<!-- Seasar (Webアプリ、ライブラリ関係なく) -->
<dependency>
    <groupId>org.seasar.dbflute</groupId>
    <artifactId>utflute-seasar</artifactId>
    <version>0.4.6</version>
    <scope>test</scope>
</dependency>
<!-- SpringでWebアプリ -->
<dependency>
    <groupId>org.seasar.dbflute</groupId>
    <artifactId>utflute-spring-web</artifactId>
    <version>0.4.6</version>
    <scope>test</scope>
</dependency>
<!-- GuiceでWebアプリ -->
<dependency>
    <groupId>org.seasar.dbflute</groupId>
    <artifactId>utflute-guice-web</artifactId>
    <version>0.4.6</version>
    <scope>test</scope>
</dependency>
サンプルコード、Java8スタイルで書いちゃいました^^。
その方が、ブログとかに書くとき楽なんですもの。

Java8リリースおめでとう!
(まだ Java8 ぜんぜんさわってないけど。。。)

#
# 【追記】
# 取り急ぎ、UTFluteのオフィシャルページを作りました。
# 環境構築のやり方が載っています。
#  -> UTFlute | DBFlute
#