JUnit 4.1 で学ぶ JavaSE 5.0

※ここに記載しているソースコードは、JUnitから転載したものです。

概要

JUnit4.1のソース解析にチャレンジしてみようと思います。これによって、デザインパターン、JavaSE5.0のプログラミング手法を習得してしまおうという試みです。

JUnit概要

JUnitは言わずと知れたJavaのテストツールです。ユニットテストを簡単に行うためのライブラリのようなものです。JUnitの作者は、Javaの世界では有名な方々。きっと参考になるはず!!というわけでチャレンジです。

ソースコードの取得

ソースコードは、ZIPになっているものを探しましたが見当たらず・・・。結局、sourceForge.netの公開されているCVSへanoymouseで接続し、junit4.1のソースコードをチェックアウトしてきました。

まずは、サンプルで動作確認

ソース解析のとっかかりとして、一番良いのは動作させてみることでしょう。というわけで、ソースの中からサンプルらしきクラスを探します。

見つけたのは、ListTest.javaというクラス。どうやら簡単そうです。とっかかりには十分です。

package org.junit.samples;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.List;

import junit.framework.JUnit4TestAdapter;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

/**
 * A sample test case, testing <code>java.util.Vector</code>.
 *
 */
public class ListTest {
	protected List<Integer> fEmpty;
	protected List<Integer> fFull;
	protected static List<Integer> fgHeavy;

	public static void main (String... args) {
		junit.textui.TestRunner.run (suite());
	}
	
	@BeforeClass public static void setUpOnce() {
		fgHeavy= new ArrayList<Integer>();
		for(int i= 0; i < 1000; i++)
			fgHeavy.add(i);
	}
	
	@Before public void setUp() {
		fEmpty= new ArrayList<Integer>();
		fFull= new ArrayList<Integer>();
		fFull.add(1);
		fFull.add(2);
		fFull.add(3);
	}
	public static junit.framework.Test suite() {
		return new JUnit4TestAdapter(ListTest.class);
	}
	@Ignore("not today") @Test public void capacity() {
		int size= fFull.size(); 
		for (int i= 0; i < 100; i++)
			fFull.add(i);
		assertTrue(fFull.size() == 100+size);
	}
	
	@Test public void testCopy() {
		List<Integer> copy= new ArrayList<Integer>(fFull.size());
		copy.addAll(fFull);
		assertTrue(copy.size() == fFull.size());
		assertTrue(copy.contains(1));
	}
	
	@Test public void contains() {
		assertTrue(fFull.contains(1));  
		assertTrue(!fEmpty.contains(1));
	}
	@Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
		int i= fFull.get(0);
		assertTrue(i == 1);
		fFull.get(fFull.size()); // Should throw IndexOutOfBoundsException
	}
	
	@Test public void removeAll() {
		fFull.removeAll(fFull);
		fEmpty.removeAll(fEmpty);
		assertTrue(fFull.isEmpty());
		assertTrue(fEmpty.isEmpty()); 
	}
	@Test public void removeElement() {
		fFull.remove(new Integer(3));
		assertTrue(!fFull.contains(3)); 
	}
}

このソースを実行してみましょう。

見てわかるとおり、5つあるテストのうち、4つしか実行されていないことがわかります。実行されていないのは、capacityメソッドです。他のメソッドと見比べてみると、その原因がわかります。「@Ignore("not today") 」という記述があります。この記述があることで、このメソッドがテストとして実行されなくなります。このようにメソッド等にメタ情報を付加する機能をアノテーションといいます。

annotation アノテーション

アノテーションは、メソッドなどにメタ情報を付加することができる機能です。JUnitでの使用方法を見てみましょう。「Ignore」アノテーションは、テストを実行したくない場合に付けるアノテーションです。定義を見てみましょう。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
	/**
	 * The optional reason why the test is ignored.
	 */
	String value() default ""; 
}

「@Retention(RetentionPolicy.RUNTIME)」は、リフレクションにより取得可能であることを示します。「@Target(ElementType.METHOD)」は、メソッドに対してのアノテーションであることを示します。アノテーションの定義は、インターフェースの定義に似ています。「@interface 」と定義します。

アノテーションの定義には、いくつかの決まりがあります。

* メソッドの宣言には、パラメータ、throw節を設定できない。
* 戻り値の型は、プリミティブ型、String、Class、列挙型、アノテーション、これらの配列のみ。
* デフォルトの値を指定可能。「default」と記載。
* 要素は、省略可能。その場合は、マーカー注釈型と呼ばれる。この場合、()の省略も可能
* 要素が単一の場合は、「value」という要素にしなければいけない。この場合、呼び出し側で「=」の記述を省略可能

Ignoreアノテーションの読み込み

では、Ignoreアノテーションは、どのようにして読み取られるのでしょうか?利用する側を見てみましょう。

調べてみると、TestIntrospectorクラスで読み取られていることがわかります。コードは次のとおりです。

public class TestIntrospector {
	private final Class<?> fTestClass;
//  ・・・中略
	public boolean isIgnored(Method eachMethod) {
		return eachMethod.getAnnotation(Ignore.class) != null;
	}
//  ・・・