Ruleを使ったJUnit4.7以降の拡張方法
前回はテストメソッド実行用Statementクラスを直接拡張しましたが、今回は@Ruleアノテーションを使った拡張方法のエントリーを書いてみます。
JUnit4.7以降のバージョンには@RuleというアノテーションとMethodRuleというインターフェースが用意されています。
(11/2 追記:4.9以降はMethodRuleが非推奨になっています。MethodRuleの替わりにTestRuleというのが出来ているので4.9以降を使用する場合はTestRuleを使うようにしましょう。インターフェースのシグネチャが多少違いますが、やってることは同じです。)
JUnit4.7以降はMethodRuleの代表的な実装としてExpectedExceptionがあります。こやつはテストケース内での例外をテストしてくれるやつですが4.6までだと
@Test(expected=IllegalArgumentException.class) public void exceptionTest(){ new Hoge().invoke(); }
上記のように例外の型だけしか検査できず、例外のメッセージをアサートする場合は自力でいろいろやらないといけませんでした。しかしExpectedExceptionを使用すると下記のようになります。(@Ruleを付加するフィールドはpublicでなければいけません。)
@Rule public ExpectedException exception = ExpectedException.none; @Test public void exceptionTest(){ exception.expect(IllegalArgumentException.class); //このコードでExceptionの型をテスト exception.expectMessage("えらー"); //このコードでエラーメッセージをテスト可能 new Hoge().invoke(); }
んで、上記をふまえた上で前回のような、あるテストクラス内で特定のメソッド(テストケース)のみでいろいろやりたいといった場合に@RuleとMethodRuleを実装したクラスを使ったやり方は下記のようになります。
まずはRuleクラスの作成
public class HogeRule implements MethodRule{ @Override public Statement apply(final Statement base,final FrameworkMethod method,final Object target) { return new Statement() { @Override public void evaluate() throws Throwable { if(method.getAnnotation(MyAnnotation.class) != null) System.out.println("Annotation 発見。"); System.out.println("かいし〜"); base.evaluate(); System.out.println("しゅーりょ〜"); } }; } }
(11/2 追記 4.9以降でMethodRuleではなくTestRuleを使う場合は下記のようになります。)
public class HogeRule implements TestRule{ @Override public Statement apply(final Statement base,final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { if (description.getAnnotation(MyAnnotation.class) != null) { System.out.println("Annotation 発見。"); } System.out.println("かいし〜"); base.evaluate(); System.out.println("しゅーりょ〜"); } }; } }
んで、このHogeRuleを使うテストクラスは下記のようになります。
public class RuleTest { @Rule public HogeRule rule = new HogeRule(); @BeforeClass public static void beforeClass(){ System.out.println("before class!"); } @AfterClass public static void afterClass(){ System.out.println("after class!"); } @Before public void before(){ System.out.println("before!"); } @After public void after(){ System.out.println("after!"); } @MyAnnotation @Test public void test1() { System.out.println("test1"); } }
これを実行した場合、コンソールには下記の順番で出力されます。
before class! Annotation 発見。 かいし〜 before! test1 after! しゅーりょ〜 after class!
注意しないといけないのはHogeRule内の無名Statementクラス内の出力処理(Annotation 発見。)は@Beforeよりも先に呼ばれる点でしょうか。
こんな感じで@RuleアノテーションとMethodRuleを使えばいろいろ拡張することが可能になります。
しかし、個人的にpublicフィールドというのがどうしても嫌なんですよねぇ〜。privateフィールドもOKにするにはかなり拡張しないといけなくなります。そのくらい別にいいじゃんという意見はよくわかるのですが・・・・。ただし、4.7以降を使う場合はやはり前回のやり方よりも今回の@Ruleを使う方がJUnitの作法としては良いはず・・・。う〜ん悩ましいです。
なお、MethodRuleの拡張方法はorg.junit.rules.ExpectedExceptionクラスかorg.junit.rules.TestWatchmanクラスをみると非常にわかりやすいと思います。
Wicket1.5をいろいろ見てみる - その4 RequestCycleの生成
前回RequestCycleに関してちょこっとエントリーを書きましたが、そもそもRequestCycleの生成方法が1.5から変わっているのでそこらへんについて書いていこうと思います。
1.4までは独自にRequestCycleを拡張して使用したい場合は下記のようにWebApplicationのnewRequestCycleをオーバーライドすることになっていました。
public class MyWebApplication extends WebApplication{ //いろいろ省略 @Override public RequestCycle newRequestCycle(final Request request, final Response response){ //独自のRequestCycleを返す。 return new MyWebRequestCycle(this, (WebRequest)request, (WebResponse)response); } }
1.5からは上記のnewRequestCycleメソッドが無くなり、代わりにcreateRequestCycleというメソッドが出来ているのですが、こちらのメソッドはfinalになっています。
また、RequestCycle自体はIRequestCycleProvider経由で取得するようになっているため独自のRequestCycleを使用する場合はgetRequestCycleProviderをオーバーライドし、独自のIRequestCycleProviderを返すようにします。
public class MyWebApplication extends WebApplication{ public IRequestCycleProvider getRequestCycleProvider(){ //getメソッドだけなので無名クラスで定義 return new IRequestCycleProvider(){ public RequestCycle get(final RequestCycleContext context){ return new MyRequestCycle(context); } }; } }
特殊な事をやろうとする以外はそうそう独自のRequestCycleを使用することは無いかとは思いますが、上記のようにProviderを経由することで独自のRequestCylceを返すことが出来るようになります。
Wicket1.5 RC5.1がリリース
Wicket1.5RC5.1がリリースされました。MLにてアナウンスもされております。
変更内容
Wicket1.5 RC5.Xが出そう??
Wicket1.5の正式リリースまではもう少し時間がかかりそうですね。
1.5 RC5.1のリリース投票を本家ではやってるみたいです。Wiqueryとの親和性の部分で議論があるみたいですが、まだ1.5の正式リリースではなくRCとしてのリリースみたいです。
でもなぜRC5.1?という疑問がわくのですがRC5のリリース投票をやったけど反対票があり、それに対する修正込みということで5.1みたいです。branchを切ってから投票して、それから正式アナウンスみたいな流れで進んでるのでRCにしては珍しくX.1とかX.2とかでリリースされるわけですね。
来月中くらいには1.5の正式リリースを期待したいところですねぇ。
Lombok すげー
面白いライブラリを見つけたのでメモ。
lombokというライブラリで、こいつが何をするかというとアノテーションを付けるとアクセサ(getter,setter)やhashCode、equalsやtoStringがバイナリレベルで自動生成される(ソースコード上には現れない)というもの。Stream系のclose処理も自動でやってくれちゃう優れもの!
Project Lombok
Java の冗長性を排除する手軽な方法
NetBeansでlombokを使う - Sacrificed & Exploited
環境に関してですが、NetBeans7+Mavenの場合はlombokの依存性をpomに追加するだけで簡単に使えるようになりました。
pom.xml
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>0.9.3</version> <scope>provided</scope> </dependency> </dependencies>
例えば下記Employeeクラスがあったとして
public class Employee { private String name; private int age; }
上記を使うクライアントクラスが下記だとするとgetNameメソッドとか呼び出しちゃったりした日にはコンパイラーに怒られちゃうのがJavaであって、そこらへん(アクセサとか書かないといけない)とかが「Javaダセーw」とか言われちゃう原因の一つだったりするわけです><
public class App { public static void main(String[] args) { Employee e = new Employee(); e.getName(); //こんぱいるえらー } }
しかし、lombokを使えば上記Employeeを下記のように修正すると上記コンパイルエラーから開放されるわけですね。
import lombok.Data; public @Data class Employee { //@Dataを付けるだけ private String name; private int age; }
public class App { public static void main(String[] args) { Employee e = new Employee(); e.getName(); //エラーにはなりません。 } }
(もちろんEmployeeを保存するだけでApp側では認識されるようになります。)
@Dataはgetter、setter、hashCode、equqals、toStringを生成し、final定義したフィールドはそのフィールドを引数にとるコンストラクタの生成を行います。
ちなみにgetterのみやsetterのみもあり、アクセスレベルの指定も可能だったります。
private @Getter @Setter int customerId; //getter setterが生成される。 private @Setter(AccessLevel.PROTECTED) String customerName; //protectedなsetterが生成される。
ちなみにJPA2.0(eclipselink2.2)を使って対象のエンティティのgetterとかsetterとかを削除してfindとかpersistとかやってみましたが問題ありませんでした。
JPAのエンティティをDDDにおけるエンティティとかバリューオブジェクトに適用する際に可読性上において邪魔くさいsetterとかgetterとかを無くすという使い道もあるかもです。
「別にJPAのエンティティとかツール使って自動生成するし、getterとかsetterとかもIDEから生成するから意味なくね?」とか「アクセサが嫌ならScalaとかRubyやれYO!」という意見は今回は無しの方向でお願いしますw。
最後にリソースの自動開放はこんな感じです。
@Cleanup FileReader reader=new FileReader("src/main/java/com/mycompany/lomboksample/App.java"); char buf[]=new char[32]; while(reader.read(buf)!=-1){ System.out.print(buf); }
ちなみにJava7での新文法はこんな感じ。
try(FileReader reader=new FileReader("src/main/java/com/mycompany/lomboksample/App.java")){ char buf[]=new char[32]; while(reader.read(buf)!=-1){ System.out.print(buf); } }
また、@Dataとかつけてるけどgetter自分で定義した場合は当然ながら自分で定義したgetterが呼ばれます。また、@Dataが付いてるクラス内にブレークポイント張ってデバッグかけたら変な所で止まったりしないかなぁとか思いましたが綺麗に止まりました。lombokは結構面白いライブラリーだと思います。
逆コンパイルとかかけてみたいなぁ〜。とか思ったところで今日は終了します。
Wicket1.5をいろいろ見てみる - その3 RequestCycleとIRequestCycleListener
今回はRequestCycle。
1.4まではReqeustCycleはその処理のほとんどをRequestCycleProcesserへ委譲していましたが、1.5からはRequestCycleProcessor自体がなくなっているようです。
また、onRuntimeExceptionも無くなっていて替わりにListenerが登場しています。
1.4にもあったonBeginRequestやonEndReqeustは存在したままですがIRequestCycleListenerを使ったほうがよいかと思われます。ちなみにIRequestCycleListenerのonBeginRequestとRequestCycleのonBginRquestの両方を拡張した場合はlistenerのonBeginRequestの直後にRequestCycleのonBeginRequestが実行される形になります。
IRequestCycleListenerにはその他に下記のようなメソッドが定義されています。実際に実装するのはAbstractRequestCycleListenerになると思われます。
- void onBeginRequest(RequestCycle cycle)
- RequestTargetに替わる(?)RequestHandlerの解決直前に実行される。RequestCycle#onBeginRequestよりもこちらが先に実行される。
- void onEndRequest(RequestCycle cycle)
- RequestCycle#onDetachの中で実行される。RequestCycle#onEndRequestの実行直後にこちらが実行される。
- void onDetach(RequestCycle cycle)
- RequestCycle#onDetachの処理の最後に実行される。
- void onRequestHandlerResolved(IRequestHandler handler)
- RequestHandlerが解決された直後に実行される。RequestHandlerが解決されなかった場合は実行されない。
- void onRequestHandlerScheduled(IRequestHandler handler)
- 各RequestHandlerがスケジューリング(?)されたタイミングで実行される。
- IRequestHandler onException(RequestCycle cycle, Exception ex)
- RequestCycleのprocessRequest内で例外が発生した場合に実行される。例外用のRequestHandlerを返すことができる。
- void onExceptionRequestHandlerResolved(IRequestHandler handler, Exception exception);
- 上記onExceptionにて例外用のRequestHandlerが解決(生成)された場合に実行される。
RequestCycleもリファクタリングと拡張がいろいろ施されていて1.4と比べながらソースを見ると凄く勉強になります。
JUnit4.5以降の拡張方法
以前に書いたJUnit4の拡張方法に関してコメントにて質問があったので4.5以降のバージョンを書いておきます。せっかくコメント頂いたのに全然気づかずに無視したみたいな形になって申し訳ありませんm(_ _)m
まず、やりたいことの前提として「テストクラス内において特定のテストケースのみで事前処理や共通チェックを行いたい」と言った場合に、どこらへんを拡張すればよいかを以前書きました。しかし、JUnit4.5からはJUnit4ClassRunnerは非推奨になり4.8.2時点ではクラスも削除されています。なので4.8.2ベースでエントリーし直しをしてみます。
まず、独自のアノテーションを作成します。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation { }
次に実行時にアノテーションの判定やら独自の処理やらを実行するクラスとしてorg.junit.runners.model.Statementを継承して作成します。
やってることは基本的に前回のエントリーと変わりません。
public class MyStatement extends Statement{ private final FrameworkMethod testMethod; private final Object target; public MyStatement(FrameworkMethod testMethod,Object target) { this.testMethod = testMethod; this.target = target; } @Override public void evaluate() throws Throwable { if(testMethod.getAnnotation(MyAnnotation.class) != null){ System.out.println("アノテーション発見!"); } testMethod.invokeExplosively(target); } }
次に、上記で作成したStatementを使用する独自のRunnerを作成します。
継承元はJUnit4ClassRunnerからBlockJUnit4ClassRunnerに変更です。
public class MyTestRunner extends BlockJUnit4ClassRunner{ public MyTestRunner(Class<?> klass) throws InitializationError { super(klass); } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { return new MyStatement(method, test); } }
上記を元にテストを書いてみます。
@RunWith(MyTestRunner.class) public class MyTestRunnerTest { @Before public void before(){ System.out.println("-- before --"); } @Test public void testA(){ System.out.println(" testA "); } @MyAnnotation @Test public void testB(){ System.out.println(" testB "); } }
上記を実行するとコンソールには下記のように表示されます。
-- before -- testA -- before -- アノテーション発見! testB
と言う具合で拡張出来るのですが、@Ruleとかを駆使したいするのがホントはいいんだろうかとも思います。まだ@Ruleの使用方法は把握しきれてないので後日エントリーを書こうと思います。(でも、publicフィールドってのが個人的に気に入らないんですけどねぇ)