データのインポート/クリーン処理の詳細
DBUnitを使用してデータのインポートおよびクリーンしているコードについて説明します。TestDataクラスはDBUnitを使用してデータをインポートする処理とクリーンする処理を共通化しているクラスです。まずはソースをさっと読んでみてください。
01: package com.oisix.common;
02:
03: import java.io.File;
04: import java.io.IOException;
05:
06: import org.dbunit.IDatabaseTester;
07: import org.dbunit.JndiDatabaseTester;
08: import org.dbunit.dataset.DataSetException;
09: import org.dbunit.dataset.IDataSet;
10: import org.dbunit.dataset.excel.XlsDataSet;
11: import org.dbunit.operation.DatabaseOperation;
12:
13: import java.util.TimeZone;
14:
15: public class TestData {
16:
17: private IDatabaseTester dbTester;
18:
19: private String fileName;
20:
21: public TestData(String fileName) {
22: this.fileName = fileName;
23: }
24:
25: public void load() {
26: dbTester = new JndiDatabaseTester(null, "java:/webappPool", TestDB.USER_ID);
27: File testData = new File(fileName);
28: IDataSet dataSet;
29: try {
30: dataSet = new XlsDataSet(testData);
31: } catch (DataSetException e) {
32: throw new RuntimeException(e);
33: } catch (IOException e) {
34: throw new RuntimeException(e);
35: }
36: dbTester.setDataSet(dataSet);
37: dbTester.setSetUpOperation(DatabaseOperation.INSERT);
38: TimeZone t = TimeZone.getDefault();
39: TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
40: try {
41: dbTester.onSetup();
42: } catch (Exception e) {
43: throw new RuntimeException(e);
44: }
45: TimeZone.setDefault(t);
46: }
47:
48: public void cleanup() {
49: dbTester.setTearDownOperation(DatabaseOperation.DELETE);
50: try {
51: dbTester.onTearDown();
52: } catch (Exception e) {
53: throw new RuntimeException(e);
54: }
55: }
56: }
17: private IDatabaseTester dbTester;
まず上から見ていきましょう。17行目でIDatabaseTesterフィールド変数を宣言しています。IDatabaseTesterはデータのインポートおよび、クリーン処理を実装しているクラスのインターフェースです。
19: private String fileName;
続いて19行目はfileNameというフィールド変数を宣言しています。こちらについては、ロードするテストデータのファイル名を保持するフィールドです。
21: public TestData(String fileName) {
22: this.fileName = fileName;
23: }
21~23行目は上で説明したとおりTestDataクラスのコンストラクタで引数でロードするファイルのファイル名が渡されます。
25行目から46行目のload()を見ていきましょう。このメソッドは、テストデータをロードするメソッドです。DBUnitを使ってテストデータをインポートする処理が書かれており、重要な部分になります。
26: dbTester = new JndiDatabaseTester(null, "java:/webappPool", TestDB.USER_ID);
26行目でJndiDatabaseTesterのインスタンス作っています。引数には、jndi-properties、jndi-name、schema-nameを指定します。このクラスはjndiを使用してデータベース接続をする場合に使用します。
27行目から35行目は、インポートするExcelファイルを読み込む処理になります。
30: dataSet = new XlsDataSet(testData);
「xls」形式のデータをインポートする場合は、30行目のようにXlsDataSetクラスを使用します。
36: dbTester.setDataSet(dataSet);
次に36行目は、インポートするデータをセットする処理で、ここでセットしたデータセットが後の処理でデータベースにインポートされます。
37: dbTester.setSetUpOperation(DatabaseOperation.INSERT);
37行目ではテストケース実施前のsetup時のオペレーションをDatabaseOperationクラスの定数で指定しています。引数にINSERTを指定しているので、46行目のonSetup()メソッド実行時に36行目でセットしたデータがデータベースにinsertされます。DatabaseOperationの定数には他にも種類がありますので、詳しくはDBUnitのJavadocを参照してください。
38: TimeZone t = TimeZone.getDefault();
39: TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
38、39行目ではデフォルトのTimeZoneに"GMT"を指定しています。こうしないと、ロードしたデータの日付がズレるためです。詳しくは弊社山下のブログの記事を見ていただければと思います。
45: TimeZone.setDefault(t);
45行目は変更したTimeZoneを元に戻しています。
最後に41行です。この処理で実際にデータベースへテストデータをインポートしています。この処理では37行目でDatabaseOperation.INSERTを指定したのでinsertが行われますが、UPDATE、DELETEなどその他もできますので、必要に応じて変更してください。
load()メソッドの説明は以上です。約20行くらいの短い記述で簡単にDBUnitを使用してテストデータをインポートすることができました。
続いて、load()メソッドでロードしてデータをクリーンする処理の説明をしたいと思います。クリーン処理は48~55行目のcleanup()メソッドに記述されています。では49行目の処理から見ていきましょう。
49: dbTester.setTearDownOperation(DatabaseOperation.DELETE);
この処理は、tear down時に呼び出すデータベースオペレーションをDatabaseOperationの定数: DELETEで指定しています。
36: dbTester.setDataSet(dataSet);
load()メソッドの36行目でインポート時にセットしたデータが削除の対象になります。
51: dbTester.onTearDown();
最後に51行目で、load()メソッドでインポートしたテストデータをクリーンしています。
これでテストデータのクリーン処理の説明を終わります。こちらの処理も非常に短い記述で簡潔に書くことができます。
オイシックスでのDBUnitの導入事例
記事の最後に、弊社でのDBUnit導入時に起こった問題について紹介させていただきたいと思います。
JUnit導入当初、ユニットテスト用のスキーマを1つ用意して共有して使っていました。しかし、導入後まもなく各開発者の環境で行うユニットテストの実施で失敗するケースが増えてきました。原因を調査してみると、DBunitで同じテーブルへのloadが重なって一意性制約エラーが起こっていたり、登録処理のテストケースで作られたデータの消し忘れをしていたのが原因でエラーになっていました。
図で説明すると以下のようになります。
DBUnitのデータロードがぶつかることは普通に想定されることなので、個人用スキーマを作って回避しようとの話になりましたが、現状、テストDBのリソースが足らないことがあったため、JUnitテスト用のスキーマを複数作ることにしました。Jenkins用に1つ、内部開発者用に5つ、オフショア開発用に1つの構成です。このことによってDBUnitのロードでぶつかることはなくなりました。
また、ゴミデータの消し忘れについてはルールを作り防ぐようにしました。開発途中の段階ではどうしても消し忘れは起こってしまうと思いますので、JenkinsにJUnitのテスト用スキーマのクリーンジョブを作り、開発者それぞれがJUnitのスキーマを使うときには、まずはクリーン処理を流した後に、そのテストスキーマを使うようにしております。そのことによってこれらの問題は起こらなくなりました。
まとめ
今回は、DBUnitを使用して、テストデータをデータベースにインポートする方法、およびインポートするデータをクリーンする方法を紹介させていただきました。DBUnitの使い方は理解できたのでしょうか。
次回は実践編として、検索系機能のテストケースの実装について説明したいと思います。

