今月のJava読書会では時間があまりそうなので、ネタを検討中です。ネタ候補の一つにアノテーション・プログラミングの事例を考えています。そこで、JUnit 4を先取りしてその実装の一部を調べています。
JUnit 4は開発途上のため、CVSからソースを入手してビルドします。9月に一度ビルドしていたのですが、今日更新(cvs update)をかけてビルドしたらエラーの山。cvsはupdateする際に-dオプションを付けないと新しくリポジトリに追加されたディレクトリは作業ディレクトリに持ってきてくれません。そこで、-dオプションを付けて再度cvs updateを実行し、それからantを実行してビルドしました。
JUnitのソースコードの入手
http://sourceforge.net/cvs/?group_id=15278にcvsによる入手方法が記載されているので、それに従ってソースを入手します。ただし、JUnit 4は、既存のJUnitのリポジトリにおいてブランチされているので、cvsで入手する際に"Version4"のブランチ名を指定します。
アノテーションをどこで使っているか?
ぱらっと見た範囲では、org.junit.runner.internal.TestClassRunnerが核心のようです。こいつのrunメソッドがテストケースクラス個々に対応して呼ばれるようです。エラー処理や付加的な処理を除いたrunメソッドの骨格は以下のような感じです。
public void run() { runBeforeClasses(); runTestMethods(); runAfterClasses(); }
runBeforeClassesが、従来のsetUpメソッド相当の呼び出し、runAfterClassesが従来のtearDownメソッド呼び出しに相当します。
runBeforeClassesメソッドは同じくTestClassRunnerで定義されており、骨格部分は以下のようになります。
public void runBeforeClasses() { List<Method> beforeMethods = fTestIntrospector.getTestMethods(BeforeClass.class); for (Method method : beforeMethods) method.invoke(null); }
BeforeClassは、アノテーション型です。
TestIntrospecotrクラスのgetTestMethodsメソッドは、引数にアノテーション型を取りまり、引数で指定されたアノテーションが付与されるメソッドを返却します。骨格部分は以下のようになります。
public List<Method> getTestMethods(Class<? extends Annotation> annotationClass) { List<Method> results = new ArrayList<Method>(); for (Class eachClass : getSuperClasses(fTestClass)) { Method[] methods = eachClass.getDeclaredMethods(); for (Method eachMethod : methods) { Annotation annotation = eachMethod.getAnnotation(annotationClass); if (annotation != null && isShadowed(eachMethod, results)) results.add(eachMethod); } } } return resuls; }