Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

TestNGで使えるアノテーションの作り方

DbUnit + TestNGのライブラリでは、@SetUpOperationなどのアノテーションを使えるわけですが、これはTestNGでリスナーインタフェースが用意されているおかげです。


TestNGでは、リスナーインタフェースが4つ用意されています。

リスナー 説明
IMethodInterceptor テストメソッド実行前に呼び出されるリスナー。実行するテストメソッドを選択することができる。
IReporter テスト結果のレポート処理に関するリスナー。
ISuiteListener テストスイートの実行前と終了後に呼び出されるリスナー。
ITestListener 各テストやテストメソッド実行前と終了後に呼び出されるリスナー。

ちなみにこれら4つはすべてITestNGListenerのサブインタフェースです。


なので、たとえばテストメソッドごとにつける@SetUpOperationなどは、テストメソッドごとに解釈する必要があるので、ITestListenerを使います。
ITestLisnerには7つのリスナーメソッドがあります。

メソッド 説明
onStart(ITestContext context) テストクラスをインスタンス化した後に呼び出される。
onFinish(ITestContext context) すべてのテストを実行した後に呼び出される。
onTestStart(ITestResult result) 各テストメソッドを実行する前に呼び出される。
onTestFailedButWithinSuccessPercentage(ITestResult result) テストが成功確率以内なら呼び出される。*1
onTestFailure(ITestResult result) テストメソッドが失敗したら呼び出される。
onTestSkipped(ITestResult result) テストメソッドをスキップしたら呼び出される。
onTestSuccess(ITestResult result) テストメソッドが成功したら呼び出される。

このうち、テストメソッドごとにアノテーションを解釈するので、onTestStart()を実装する必要があります。


さて、@SetUpOperationですが、こんなアノテーションです。

@Retention(RUNTIME)
@Target(METHOD)
public @interface SetUpOperation {

	DatabaseOperationType value() default DatabaseOperationType.NONE;

	String pathname() default "";

	String extension() default "";

}

DatabaseOperationTypeはCLEAN_INSERTとかREFLESHとかのEnumです。pathnameは初期データファイルのパス、extensionは拡張子を指定します。


で、このアノテーションをonTestStart()で解釈します。

public class DbUnitNGTestListener implements ITestListener {

	public void onTestStart(ITestResult result) {

		SetUpOperation setUpOperation =
			this.getAnnotation(result, SetUpOperation.class);

		if (setUpOperation == null) {
			return;
		}
		DatabaseOperationType type = setUpOperation.value();
		String extension = setUpOperation.extension();
		String pathName = setUpOperation.pathname();
		readFileForDatabase(result, type, extension, pathName);
	}

	protected <T extends Annotation> T getAnnotation(ITestResult result,
			Class<T> annotationClass) {

		ITestNGMethod testNGMethod = result.getMethod();
		Method method = testNGMethod.getMethod();

		return method.getAnnotation(annotationClass);

	}

onTestStart()には引数としてITestResultオブジェクトが渡されます。
この中に、今から実行するテストメソッドが入っています。
呼び出しているgetAnnotation()メソッドを見てみましょう。
ITestResult#getMethod()を呼び出します。この戻り値はjava.lang.reflect.Methodではなく、ITestNGMethodです。
ITestNGMethodからアノテーションはとれないので、ITestMethod#getMethod()を呼び出してMethodを取得します。


そして、Method#getAnnotaion(Class)でアノテーションクラスであるSetUpOperation.classを渡し、アノテーションを取得します。
もし、@SetUpOperationがついていないメソッドなら、nullが返ります。
あとはアノテーションから属性の値(DatabaseOperationType, pathname, extension)を取得し、ファイルを読み出してデータベースに登録してます(readFileForDatabase())。


このように、リスナーとアノテーションを使えば、うまくテスト実行前の初期化処理ができます。
こういう仕組みは、JUnitにはないので、TestNGの利点だと思います。
ちなみに、作ったリスナーを利用するには、testng.xmlに記述します。

<suite name="DbUnitNG">
	<listeners>
		<listener class-name="org.dbunitng.listeners.DbUnitNGTestListener"></listener>
	</listeners>
</suite>

*1:TestNGではテストの成功確率、というのがあって、テストが失敗しても回数としてその確率以内なら成功となる。要は複数回のテストが前提。