SSブログ

DBUnitを利用した単体テストに関して [Test]

DBを使用した単体テストをおこなう場合には、テーブルに格納するデータを変更する必要が生じます。テスト実施前のテーブルのデータに影響を与えたくない場合には、テストの方法として次の2種類の方法が考えられます。

  1. テストケース実行時にDBMSに対しコミット要求をおこない、検証時にはコミットされたデータを検証する方法。この場合には、テスト実行前にテーブルのバックアップをおこない、テスト実行後に復元をおこなう。
  2. テストケース実行時にはデータを追加・更新・削除し(コミットはおこなわない)、検証後、トランザクションをロールバックする方法

1.の方法を選択すると、テスト実行前にテストデータを投入するテーブルのバックアップをおこない、テスト終了時にバックアップからテーブルの内容を復元する必要が生じます。2. の方法を選択すると、テーブルのバックアップ・復元等、初期化、終了に伴うコストを 省略すること ができるため、1. に比べるとテストの実行時間を軽減することができます。(テストクラスごとにバックアップ・復元をおこなうのであれば、必ずしも大きな実行時間を要することはありません。)しかしながら、トランザクション制御、トランザクションスコープ、分離レベル(isolation level)によっては(1)の方法をとらざるをえない場面があるかと思います。

今回は、JUnitを基に拡張されたDBUnitを利用してどのように実現すべきか検討してます。まず 1. の方法でデータのバックアップ、復元をJUnitの前後処理でおこなう方式に関して(自分の復習を兼ねて)以下に整理してみます。最後に 2. ロールバックする方法に関しても少し整理してみます。

1. テストケース実行時にDBMSに対しコミット要求をおこない、検証時にはコミットされたデータを検証する方法

まず、テーブルのバックアップおよび復元の単位をどの単位にするかを考えてみます。テストケースごとにバックアップ、復元をおこなってもコストがかかるだけですので、必要でない限りテストケースの全体の前処理でバックアップを、後処理で復元をおこなうよう必要があります。次に実現方法に関して検討してみます。

実現方式としては、以下の2通りの方式を選択できます。

  1. TestNGの@Configurationタグを利用
  2. TestSetupクラスの利用。

1.TestNGの@Configurationタグを利用

TestNGを利用すれば、@Configurationアノテーションで、テストケースタグに含まれる全てのテストクラスの前後で実施される動作を定義できます。2のTestSetupクラスを使用した方式でも同様のことが実現できますが、よりスマートに記述できますし、suiteメソッドがstaticである制約を回避するよう設計されています。

 @Configuration(beforeTest = true)
 @Configuration(afterTest = true)

@Configuration(beforeTest = true)
 public void oneTimeSetup() {
  //System.out.println("テストケース全体の前処理");
 }

@Configuration(beforeTest = true, afterTest = true)
 public void aroundTest() {
  //System.out.println("テストケース全体の前後でおこなう処理");
 }

2.TestSetupクラスの利用。

古典的な方法は、TestSetupクラスでテストスイートの前後で実施する処理を定義する方式です。TestCaseのサブクラス(もちろんTestSuiteでも構いません)でsuiteクラスメソッドをオーバライドし、その中でTestSuiteインスタンスを作ってからTestSetupインスタンスでラップします。(デコレーターパターン)TestSetupクラスでは、setUpメソッドを定義しテストスイートごとの前後に実施したい処理を記述します。ただしsuiteメソッドはstaticメソッドでなければならない制約がありますので、TestNGを採用できるのであれば(他にもメリットがありますので)、TestNGを選択することをお勧めします。

 public class SampleTest extends TestCase {

  static void oneTimeSetup() throws Exception {
   //バックアップ処理
  }

  static void oneTimeSetup() throws Exception {
   //バックアップしたデータの復元
  }

  //static メソッドでなければならない制約が難点です。
  public static Test suite() throws Exception {

   TestSuite suite = new TestSuite(SampleTest.class);

   TestSetup wrapper = new TestSetup(suite) {
    public void setUp() throws Exception {
     oneTimeSetup();
    }
    public void tearDown() throws Exception {
     oneTimeTearDown();
    }
   };
        return wrapper;
  }
 }

○DbUnitを用いたバックアップ処理と復元処理に関して

DBUnitには指定したテーブルの内容をファイルにエクスポートする機能およびその逆のインポートの機能があります。(具体的には下記のコードをご覧ください。)

テーブルの内容のバックアップに関しては、oneTimeSetupメソッドをご覧ください。本メソッドでは、バックアップ対象のテーブルが指定されていれば、データセットに対して追加を行い、内容をファイルに出力しています。oneTimeTearDownメソッドでは、バックアップされたファイルを基に、DBMSの内容を更新します。

トランザクションスコープに関しては、用途に応じて選択されてください。観点としては、バックアップ処理と復元処理それぞれのトランザクションをテストケースと別々にするかどうかです。(下記の例では、1つのコネクションでバックアップ、テストケースの実行、データの復元をおこなっています。)

/**
     * <p>事前処理</p>
     *
     * @throws Exception JavaNative例外
     */
    protected void oneTimeSetUp() throws Exception {

        try {
   //コネクションの取得
   conn_ = getJdbcConnection();
   
   //DBUnit用コネクションの取得
   iConn_ = new DatabaseConnection(conn_);

   //データのバックアップ
   if (getBackupTables() != null) {
    QueryDataSet partialDataSet = new QueryDataSet(iConn_);
    //バックアップ対象のテーブルを指定
    for (int i=0; i < getBackupTables().length; i++) {
     partialDataSet.addTable(getBackupTables()[i]);
    }
    //ファイルのバックアップ
    backupFile_ = File.createTempFile("BACKUP", ".xml");
    //System.out.println(backupFile_.toString());
    FlatXmlDataSet.write(partialDataSet, new FileOutputStream(backupFile_));
   }
  } catch(Exception e) {
   e.printStackTrace();
  } finally {
   //closeIDatabaseConnection(iConn_);
  }
    }
   
    /**
     * <p>事後処理</p>
     *
     * @throws Exception JavaNative例外
     */
    protected void oneTimeTearDown() throws Exception {
      try {
    //コネクションの取得
    //conn_ = getJdbcConnection();
       
    //DBUnit用コネクションの取得
    //iConn_ = new DatabaseConnection(conn_);
       
       if (getBackupTables() != null) {
     //バックアップしたデータでテーブルの内容を復元する。
     IDataSet dataSet = new  FlatXmlDataSet(backupFile_);
     DatabaseOperation.CLEAN_INSERT.execute(iConn_, dataSet);

     //バックアップファイルの削除
     backupFile_.deleteOnExit();
       }
  } catch(Exception e) {
   e.printStackTrace();
  } finally {
   //コネクションのクローズ
   closeIDatabaseConnection(iConn_);
  }
 }

 /** <p>バックアップ対象のテーブル</p>
   * サブクラスで実装。
   */
 protected String[] getBackupTables() {
  return new String[]{"TBL1", "TBL2", "TBL3"};
 }

2.テストケース実行時にはデータを追加・更新・削除し(コミットはおこなわない)、検証後、トランザクションをロールバックする方法

 テスト検証後にトランザクションをロールバックする方法に関して考察してみます。この方式を採用すると、テストケースによりテーブルの変更をおこなえる上、ロールバックによりテーブルのバックアップ・復元の工程を省くことが可能になります。ロールバックする単位に関しては、テストケースごとにテーブルに変更した内容を無効にしたいので、テストケースごとにロールバックすることになります。

具体的には以下のとおりです。まず、setUp時に、コネクションの取得後、オートコミットを無効化し、テストデータを投入します。次に、テストケースを実行し、DBの内容が想定どおりに変更されているかを検証します。最後に、tearDown時に、ロールバックをおこなうことにより、テーブルをテスト実行前の内容に戻します。

DBを利用した単体テストをおこなう際に、テスト実行前の状態を復元できることは重要です。必要な場合には、以上の方法を選択子の1つとして検討してみてはいかがでしょうか?


nice!(0)  コメント(0)  トラックバック(672) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 0

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 672

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。