Fight the Future

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

DbUnitでSQL文の結果をそのままデータセットにする

	public static void main(String[] args) throws Exception {

		JdbcDatabaseTester tester =
			new JdbcDatabaseTester(
				"com.mysql.jdbc.Driver",
				"jdbc:mysql://127.0.0.1/testframework",
				"root",
				"root");
		
		QueryDataSet dataSet = new QueryDataSet(tester.getConnection());
		// retrieve all rows from specified table
		dataSet.addTable("EMP");
		
		// retrieve rows that is result of argument query
		dataSet.addTable("EMP_UNDER_7500", "select * from emp where empno < 7500");

		output(dataSet);
	}

	protected static void output(IDataSet dataSet) throws DataSetException {
		for (String name : dataSet.getTableNames()) {
			ITable table = dataSet.getTable(name);
			System.out.print("Table name:"
				+ table.getTableMetaData().getTableName());
			System.out.println("\tRow count:" + table.getRowCount());
		}
	}

QueryDataSet#addTable()でデータベースにあるレコードをデータセットに追加します。
引数が1つのものは、DBのテーブル名を渡します。そのテーブルのレコードをすべてITableにしてデータセットに追加します。
引数が2つのものは、第1引数は任意の文字列です。それがITableの名前になります。第2引数に実行するSQLを記述します。


このサンプルを実行するとこんな感じです。

Table name:EMP	Row count:14
Table name:EMP_UNDER_7500	Row count:2

TestNGでXMLを使わずにテストを実行する

TestNGではtestng.xml(ファイル名は任意)というXMLを使ってテストを実行するのですが、
XMLを使わずにプログラミングでテストを実行することもできます。

	public static void main(String[] args) {
		TestNG testNG = new TestNG();

		testNG.addListener(new DbUnitNGTestListener());
		testNG.addListener(new PerformanceListener());
		TestListenerAdapter adapter = new TestListenerAdapter();
		testNG.addListener(adapter);

		testNG.setTestClasses(new Class[] { PerformanceTest.class });

		testNG.run();

		System.out.println("PassedTests:" + adapter.getPassedTests());
		System.out.println("FailedTests:" + adapter.getFailedTests());
		System.out.println("SkippedTests:" + adapter.getSkippedTests());

	}

TestNGオブジェクトを生成して、run()メソッドを呼ぶことでテストを実行します。
リスナークラスを追加することもできます。
ただ、普通にrun()を呼び出しただけでは、テスト成功と失敗が数でしかわかりません。
こんな感じで。

===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

なので、詳細に出力するためにはTestListenerAdapterオブジェクトをリスナーとして登録します。
TestListenerAdapterはTestNGが提供するクラスで、テスト結果に関するさまざまな情報を取り出すことができます。
このサンプルでは成功、失敗、スキップしたテストをコンソールに出力してます。

PassedTests:[[TestResult: testPerformance STATUS:1 METHOD:org.dbunitng.sample.PerformanceTest.testPerformance()]]
FailedTests:[]
SkippedTests:[]

XMLの設定をプログラミングする

ですが、TestNGクラスには、XMLで記述できたパラメータなどを設定するメソッドはありません。

<parameter name="driver" value="com.mysql.jdbc.Driver"></parameter>
<parameter name="url" value="jdbc:mysql://127.0.0.1/testframework"></parameter>
<parameter name="username" value="root"></parameter>
<parameter name="password" value="root"></parameter>

パラメータをプログラミングで設定するためには、XmlSuiteオブジェクトを利用します。

	public static void main(String[] args) {
		TestNG testNG = new TestNG();

		testNG.addListener(new DbUnitNGTestListener());
		TestListenerAdapter adapter = new TestListenerAdapter();
		testNG.addListener(adapter);

		testNG.setTestClasses(new Class[] { DbUnitNGAnnotationTest.class });

		XmlSuite suite = new XmlSuite();
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put("driver", "com.mysql.jdbc.Driver");
		parameters.put("url", "jdbc:mysql://127.0.0.1/testframework");
		parameters.put("username", "root");
		parameters.put("password", "root");
		parameters.put("defaultOperation", "CLEAN_INSERT");
		suite.setParameters(parameters);

		testNG.setXmlSuites(Arrays.asList(new XmlSuite[] { suite }));

		testNG.run();

		System.out.println("PassedTests:" + adapter.getPassedTests());
		System.out.println("FailedTests:" + adapter.getFailedTests());
		System.out.println("SkippedTests:" + adapter.getSkippedTests());

	}

XmlSuiteオブジェクトを生成し、Mapでパラメータを表します。
MapをXmlSuiteにセットし、TestNGオブジェクトにXmlSuiteをセットします。
ただ、これでは例外が発生します。

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:151)
	at java.util.AbstractList.add(AbstractList.java:89)
	at org.testng.TestNG.initializeCommandLineSuites(TestNG.java:630)
	at org.testng.TestNG.run(TestNG.java:690)

どうやらArrays.asList()で生成したリストに対してTestNGが内部で要素を追加しようとしたようです*1
なので、こうしました。

	public static void main(String[] args) {
		TestNG testNG = new TestNG();

		testNG.addListener(new DbUnitNGTestListener());
		TestListenerAdapter adapter = new TestListenerAdapter();
		testNG.addListener(adapter);

		testNG.setTestClasses(new Class[] { DbUnitNGAnnotationTest.class });

		XmlSuite suite = new XmlSuite();
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put("driver", "com.mysql.jdbc.Driver");
		parameters.put("url", "jdbc:mysql://127.0.0.1/testframework");
		parameters.put("username", "root");
		parameters.put("password", "root");
		parameters.put("defaultOperation", "CLEAN_INSERT");
		suite.setParameters(parameters);

		List<XmlSuite> list = new ArrayList<XmlSuite>();
		list.add(suite);
		testNG.setXmlSuites(list);

		testNG.run();
// the rest is ommitted...

しかし、それでも例外が発生します。

Exception in thread "main" org.dbunitng.exception.DbUnitNGRuntimeException: Required Parameters are not specified in TestNG Suite.
org.dbunitng.listeners.internal.DbUnitNGConfig@1dddba[
  driver=<null>
  url=<null>
  userName=<null>
  password=<null>
  isDbcp=false
  schema=<null>
  factory=<null>
  defaultOperation=NONE
]

Mapで設定したパラメータが取得できないようです。
テストでパラメータを利用したい場合、TestNG#setTestClasses()でテストクラスをセットするのではなく、XmlTest#setXmlTestClasses()を使う必要があるようです。


なので、最終的にパラメータを設定したテストは次のようになります。

	public static void main(String[] args) {
		TestNG testNG = new TestNG();

		testNG.addListener(new DbUnitNGTestListener());
		TestListenerAdapter adapter = new TestListenerAdapter();
		testNG.addListener(adapter);

		XmlSuite suite = new XmlSuite();
		Map<String, String> parameters = new HashMap<String, String>();
		parameters.put("driver", "com.mysql.jdbc.Driver");
		parameters.put("url", "jdbc:mysql://127.0.0.1/testframework");
		parameters.put("username", "root");
		parameters.put("password", "root");
		parameters.put("defaultOperation", "CLEAN_INSERT");
		suite.setParameters(parameters);

		XmlTest test = new XmlTest(suite);
		XmlClass[] classes =
			new XmlClass[] { new XmlClass(DbUnitNGAnnotationTest.class) };
		test.setXmlClasses(Arrays.asList(classes));

		List<XmlSuite> list = new ArrayList<XmlSuite>();
		list.add(suite);
		testNG.setXmlSuites(list);

		testNG.run();
// the rest is ommitted...

これでセットしたパラメータを用いてテストを実行できます。

*1:Arrays.asList()が生成するリストは不変リスト

DbUnitライブラリでデフォルトのDatabaseOperationを指定できます

@SetupOperationでDbUnitのDatabaseOperationを指定できるわけですが、
テストメソッドごとに毎回指定するのは手間なので、
テストスイート全体でデフォルトを設定できるようにしました。


接続情報と同様に、
testng.xmlか@DbUnitNGアノテーションで指定することができます。
もちろんデフォルトを指定しないこともできます。

こんな感じです。

@BeforeSuite
@DbUnitNG(driver = "com.mysql.jdbc.Driver", password = "root", url = "jdbc:mysql://127.0.0.1/testframework", username = "root", defaultOperation = DatabaseOperationType.CLEAN_INSERT)
public void beforeSuite() {}

アノテーションであればdefaultOperation属性にEnumのDatabaseOperationTypeを指定します。
XMLであれば要素にname「defaultOperation」で指定します。

<suite name="DbUnitNG">
	<parameter name="defaultOperation" value="CLEAN_INSERT"></parameter>
	<parameter name="driver" value="com.mysql.jdbc.Driver"></parameter>
	<parameter name="url" value="jdbc:mysql://127.0.0.1/testframework"></parameter>
	<parameter name="username" value="root"></parameter>
	<parameter name="password" value="root"></parameter>

defaultOperationの値は次の文字列を指定します。

  1. NONE
  2. UPDATE
  3. INSERT
  4. REFRESH
  5. DELETE
  6. DELETE_ALL
  7. TRUNCATE_TABLE
  8. CLEAN_INSERT


もちろん、デフォルトを指定していても@SetUpOperationや@TearDownOpearionで指定すれば、後者を優先します。

DbUnitライブラリで接続情報をアノテーションにしました

DbUnitNG プロジェクト日本語トップページ - SourceForge.JPで、データベースへの接続情報をアノテーションでも設定できるようにしました。
と同時に、スキーマも記述できるようにしました。
さらに、JDBCのURLから判断して、DbUnitのIDataTypeFactoryを自動的に設定するようにしました(後述)。

ライブラリDbUnitNGでは接続情報を設定する方法が2つあります。

  1. testng.xml
  2. アノテーション

XMLだとこんな感じです。

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="DbUnitNG">
	<parameter name="driver" value="com.mysql.jdbc.Driver"></parameter>
	<parameter name="url" value="jdbc:mysql://127.0.0.1/testframework"></parameter>
	<parameter name="username" value="root"></parameter>
	<parameter name="password" value="root"></parameter>
	<parameter name="schema" value="testframework"></parameter>
	<listeners>
		<listener class-name="org.dbunitng.listeners.DbUnitNGTestListener"></listener>
	</listeners>
	<test verbose="2" name="sample" annotations="JDK">
		<classes>
			<class name="org.dbunitng.sample.PerformanceTest">
			</class>
		</classes>
	</test>
</suite>

同じ設定をアノテーションでするとこうなります。

@BeforeSuite
@DbUnitNG(driver = "com.mysql.jdbc.Driver", password = "root", url = "jdbc:mysql://127.0.0.1/testframework", username = "root", schema = "testframework")
public void beforeSuite() {}

接続情報を記述するアノテーションは@DbUnitNGです。
XMLの記述内容と同じですが、必ず@BeforeSuiteと併用しないといけません。
@BeforeSuite + @DbUnitNGで読み取れます。


もし、XMLアノテーション両方とも使った場合、アノテーションを優先します。

IDataTypeFactory

DbUnitのIDataTypeFactoryは各データベース独自のデータ型をサポートするためのものです。
たとえば、Oracleを使っている場合、OracleDataTypeFactoryオブジェクトをDbUnitのDatabaseConfigオブジェクトにセットします。

config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,factory);

ライブラリDbUnitNGではこの設定を自動的にやります。
JDBCのURLに含まれるデータベース名から判断してます。

jdbc:sqlserver://hostname:9999 MsSqlDataTypeFactory
jdbc:oracle:thin:@hostname:1521:orcl OracleDataTypeFactory
jdbc:db2://host:9999/database Db2DataTypeFactory
jdbc:mysql://127.0.0.1/testframework MySqlDataTypeFactory
jdbc:hsqldb:hsql://127.0.0.1:9001 HsqldbDataTypeFactory
jdbc:h2:tcp://localhost:9092/MyDB H2DataTypeFactory

これら以外のデータベースならデフォルトのファクトリを使います。

DbUnitNG0.2リリース!

テストケースの追加と、バグを修正しました!
DbUnitNG プロジェクト日本語トップページ - SourceForge.JP
AssertionHelperクラスを使えばラクにアサートすることができます。

List<Dept> deptList = deptDao.listAllDept();
AssertionHelper.assertEqualsOnlyColumnsInFile(
	deptList,
	getClass(),
	"dept.xml");

単に内部ではリストをデータセットに変換して、ファイルと比較してるだけなんですけどね。
getClass()してるのは、ファイルをテストクラスと同じパッケージから探すためです。
テストクラスが「aaa.bbb.ccc.HogeTest」であれば、「aaa/bbb/ccc/dept.xml」を読み込みます。


メソッド名のとおり、ファイルに記述したカラムのみ比較します。
たとえば、BeanであるDeptクラスにこういうプロパティがあるとします。

  • dname
  • deptno
  • location
  • updateDate

updateDateは更新日付と考えましょう。
一方、XMLはこういう記述です。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<DEPT DEPTNO="10" DNAME="ACCOUNTING" LOCATION="NEW YORK" />
	<DEPT DEPTNO="20" DNAME="RESEARCH" LOCATION="DALLAS" />
	<DEPT DEPTNO="30" DNAME="SALES" LOCATION="CHICAGO" />
	<DEPT DEPTNO="40" DNAME="OPERATIONS" LOCATION="BOSTON" />
</dataset>

updateDateはありません。
なので、アサートではdname,deptno,locationのみ比較します。


DbUnitのデフォルトでは、こういうカラムが異なる場合、アサートが失敗してしまいます。
自分でDefaultColumnFilter.includedColumnsTable()とかexcludedColumnsTable()とかしないといけない。
なのでAssertionHelperに組み込んでおきました。
もちろん通常のDbUnitのアサートをしたいときは、普通にDbUnitのAssertionクラスを利用すればいいわけです。

DbUnitライブラリ用のサンプルを作成しました

trunk/DbUnitNG/sampleとsampletestディレクトリ以下にあります。
sampleはiBatisを使ったデータベースアクセスアプリです。
なので、DbUnitNGの依存ライブラリに加えてiBatisのライブラリが必要です。
DBはMySQLを使っていますが、他のDBでも動くはずです。
必要なテーブルのcreate文や初期データはsampleのorg.dbunitng.sample.sqlパッケージに入っています。

例外発生テストメソッドに見るテスティングフレームワークの進化

テストにおいては、正しく例外が発生するかもテストする必要がある。
JUnit3の時代は、僕の周りではそのテストの書き方を理解している人が少なかった印象がある。
JUnit3では例外発生テストはこのように書くものだった。

import junit.framework.TestCase;

public class TestCaseSample extends TestCase {

	public void testExpectedException() {

		Sample s = new Sample();
		try {
			s.throwException();
			fail("Exception is not thrown.");
		} catch (IllegalStateException expected) {
			assertTrue(true);
		}
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}
}

ポイントは2つあった。

  • 例外が発生するメソッドを呼び出した次の行でTestCase#fail()を呼び出す。
  • 例外発生をテストしていると読みやすくするため、catch句では例外の変数をexpectedとし、assertTrue(true)とする。

要は、例外が正しく発生しなければcatch句に行かず、fail()が呼ばれてテストが失敗する。
変数expectedやassertTrue()は読みやすさのためだが、このfailを書かずにテストしている人が多かったように感じる(そもそも例外発生のテストを書く人自体が少なかった)。
fail()がないと例外が発生しなくてもテストが成功してしまう。


こうしたややトリッキーなコードであるため、広まっていなかったと思う。
JUnit4では、@Testに期待する例外クラスを記述することができる。
同じテストをJUnit4で書くとこうなる。

import org.junit.Test;

public class JUnit4Sample {

	@Test(expected = IllegalStateException.class)
	public void testExpectedException() {

		Sample s = new Sample();
		s.throwException();
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}

}

@Testの属性としてexpectedがあり、そこに発生する例外クラスを記述する。
もし発生しなければテストは失敗する。


もちろんTestNGでも同様(というよりこっちが先駆者?)
TestNGではこうなる。

import org.testng.annotations.Test;

public class TestNGSample {
	@Test(expectedExceptions = IllegalStateException.class)
	public void testExpectedException() {

		Sample s = new Sample();
		s.throwException();
	}

	private static class Sample {
		public void throwException() {
			throw new IllegalStateException();
		}
	}
}

アノテーションの属性名が異なるだけだ。expectedExceptionsで指定する。


ただ、違いはある。
TestNGとJUnit4のもっとも大きな違い - Fight the Future じゅくのblogでも述べたように。JUnitはテストの独立性を考えているため、例外クラスは1つしか記述できない。
テストは独立しているのだから、2つの例外が同時に発生することはないし、当然だ。


一方、TestNGはexpectedException「s」となっているように、例外クラスを複数指定できる。
どれが1つの例外が発生しさえすればテストは成功になる。
テストメソッドにパラメータを渡せるため、パラメータによって発生する例外が異なることもあるからだ。

TestNGのログを出力するには

クラスパスのルートに「log4testng.properties」を置く。
log4testng.propertiesはこんな風に設定する。

# log4testng will log its own behavior (generally used for debugging this package only).
log4testng.debug=true

# Specifies the root Loggers logging level. Will log DEBUG level and above
log4testng.rootLogger=DEBUG

TestNGは特に他のログライブラリには依存していないので、これだけでOK。

TestNGの発音は「てすとえぬじー」ではなかった!

TestNG (pronounced "testing", but derived, one understands, from "Testing, the Next Generation")

Next Generation Java Testing - JavaLobby Book Review

TestNGと書いて「てすてぃんぐ」と読むようだ。


ちなみに、僕がよく使う発音の調べ方は、「XXX pronounce」でググること。
pronounceは発音って意味ね。
これで長年の疑問だったiBatisも解決できたよ(あいばてぃす)。

TestNG+DbUnitライブラリのパフォーマンス

5000件をBeanのListで取得して、XMLの期待値と比較するパフォーマンステストをやってみました。
こういうのって一概に参考にならないことも多いと思うんですけど、感触だけでも伝えられたらって感じです。

こんなテーブルに5000件入れます。

mysql> desc emp;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| EMPNO    | int(11)     | NO   | PRI | NULL    |       | 
| ENAME    | varchar(10) | YES  |     | NULL    |       | 
| JOB      | varchar(9)  | YES  |     | NULL    |       | 
| MGR      | int(11)     | YES  |     | NULL    |       | 
| HIREDATE | date        | YES  |     | NULL    |       | 
| SAL      | float(7,2)  | YES  |     | NULL    |       | 
| COMM     | float(7,2)  | YES  |     | NULL    |       | 
| DEPTNO   | int(11)     | YES  | MUL | NULL    |       | 
+----------+-------------+------+-----+---------+-------+

データはこんなのです。期待値のXMLです。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <EMP EMPNO="1" ENAME="SMITH" JOB="CLERK" MGR="7902" HIREDATE="1980-12-17" SAL="800.0" DEPTNO="20"/>
...follow similar elements
</dataset>

こういうテストです。

  • テーブルをCLEAN_INSERTして5000件のデータを入れる。
  • DAOから5000件をリストで取得する
  • リストをデータセットに変換し、XMLの期待値とアサーションする

DAOは内部でiBatisを使ってデータを取得してます。


実行結果はこうでした!

Test Suite starts.
DAO execute time : (381 ms)
PASSED: testPerformance

Test Suite ends.
execute time : (14359 ms)

テスト全体での実行時間は14秒ちょい(3回実行してもほぼこの時間でした)。
DAOのメソッドを呼び出す前後で時間を計測しましたが、それが0.4秒なので、
CLEAN_INSERTして、データセットへの変換とアサーションで14秒かかってます。


5000件でこの実行時間なら十分許容範囲じゃないです?
まさかユニットテストで5000件入れることはないし、
結合テストとかならビルドマシンやデイリービルドで実行するので、
十分実用にたえると思います。


以下実行環境や詳しいテスト内容を記述します。

僕のマシンスペックはこうです。

  • プロセッサ名: Intel Core 2 Duo
  • プロセッサ速度: 2.4 GHz
  • プロセッサ数: 1
  • 合計コア数: 2
  • 二次キャッシュ: 3 MB
  • メモリ: 2 GB

MySQLのバージョンです。

mysql> select version();
+-------------+
| version()   |
+-------------+
| 5.0.51b-log | 
+-------------+

テストクラスです。

package org.dbunitng.sample.test;

import java.util.List;

import org.dbunit.DatabaseUnitException;
import org.dbunitng.annotations.DatabaseOperationType;
import org.dbunitng.annotations.SetUpOperation;
import org.dbunitng.assertion.AssertionHelper;
import org.dbunitng.dataset.BeanListConverter;
import org.dbunitng.sample.dao.EmpDao;
import org.dbunitng.sample.entity.Emp;
import org.testng.annotations.Test;

@Test(groups = "performance")
public class PerformanceTest {

	@SetUpOperation(value = DatabaseOperationType.CLEAN_INSERT, pathname = "org/dbunitng/sample/test/result.xml")
	public void testPerformance() throws DatabaseUnitException {
		EmpDao dao = new EmpDao();
		long start = System.currentTimeMillis();
		List<Emp> list = dao.listAllEmployee();
		System.out.printf("DAO execute time : (%d ms)\n", System.currentTimeMillis() - start);

		AssertionHelper.assertEqualsOnlyColumnsInFile(new BeanListConverter(
			list).convert(), getClass(), "result.xml");
	}

}

実行時間を計測するリスナークラスです。

package org.dbunitng.sample.test;

import org.testng.ISuite;
import org.testng.ISuiteListener;

public class PerformanceListener implements ISuiteListener {

	private long start;

	public void onStart(ISuite suite) {
		System.out.println("Test Suite starts.");
		start = System.currentTimeMillis();
	}

	public void onFinish(ISuite suite) {
		System.out.println("Test Suite ends.");
		System.out.printf("execute time : (%d ms)", System.currentTimeMillis()
			- start);
	}

}

DbUnitNG 0.1 リリース!

DbUnitNGのバージョン0.1をリリースしました。

  • DbUnitのSetUpOperationとTearDownOperatonをアノテーション対応しました。
  • BeanのListをDbUnitのIDataSetに変換するBeanListConverterを実装しました。
  • DbUnitのAssertionに対するヘルパークラスAssertionHelperクラスを実装しました。

以上すべてテストコードを記述済です。


基本的な使い方はSourceForgeのWikiに記述しています。


少しアノテーションの属性でファイルを指定するのがまどろっこしいので、0.2では簡便性のために属性を増やそうと思っています。

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ではテストの成功確率、というのがあって、テストが失敗しても回数としてその確率以内なら成功となる。要は複数回のテストが前提。