DBUnitにさわる

作成 2004/9/10

今度使うらしいのでちょっとさわってみた。ちょっとだけ。

DBUnitとは?

DBUnitとは、データベースを含むプログラムの単体テストツールです。アプリケーション開発では、多くの場合、データベースを利用します。DBを更新したり、DBから値を取得する単体テストを書くとき、「テスト用のデータのセットアップ」や「更新されたデータの確認」などを行うのは、非常に骨の折れる作業です。DBUnitは、そのような骨の折れる作業を低減させてくれる方法とツールを提供しています。

インストール

DBUnitは以下のWebページからダウンロードします。

DBUnitのWebページ
http://dbunit.sourceforge.net/

ここでは現在の最新バージョンの2.1をダウンロードしました。

ここで使うテーブル

ここではMySQLを利用して、簡単なデータベースとテーブルを作成し、それを利用したテストを記述します。テーブルは以下です。

CREATE TABLE CD(
  TITLE VARCHAR(100),
  PRICE INTEGER,
  DATE DATE,
  PRIMARY KEY(TITLE)
);

ついでに以下のようなデータを入れておきます。

insert into cd values('レレレ',1000,'2004-10-10');
insert into cd values('いかだに乗ってどこまでも',2000,'2005-12-10');

テストデータの作成

DBUnitでは、まずテスト用のデータを準備します。データは基本的にXMLで記述します。データは、データベースからエクスポートするか、あるいは手で記述します。テストデータのエクスポートにはDBUnitのAntタスクが利用します。

エクスポート

DBUnitのAntタスクを使って、DBからデータをエクスポートすることができます。

エクスポートを行うAntビルドファイルの例

<project name="hoge" default="hoge">

    <taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask">
        <classpath id="class.path">
            <fileset dir="lib"/>
        </classpath>
    </taskdef>
    
    <target name="hoge">
        <dbunit driver="com.mysql.jdbc.Driver"          
                url="jdbc:mysql:///blog?useUnicode=true&characterEncoding=SJIS"          
                userid="root"          
                password="">    
            
            <export dest="data/export.xml"/>
            
        </dbunit>
    </target>
        
</project>

このタスクを実行すると、以下のファイルが出力されます。DBのデータがXML形式で出力されているのがわかります。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <cd TITLE="いかだに乗ってどこまでも" PRICE="2000" DATE="2005-12-10"/>
  <cd TITLE="レレレ" PRICE="1000" DATE="2004-10-10"/>
</dataset>

なお、XMLの出力形式には、xmlとflatの2種類があり、形式により、DBUnitからの利用法も若干異なるので注意してください。上記の例はflat(デフォルト)の出力です。

DBUnitのAntタスクは、エクスポートの他に、DTDエクスポートや、インポート、データの比較など、いろいろと操作が出来ます。DTDを出力すると、補完機能のついたXMLエディタでデータの編集がやりやすかったりします。どんなタスクが利用できるかは、以下をご覧ください。

http://dbunit.sourceforge.net/anttask.html

単体テストの記述

次にJavaで単体テストを記述します。やることは、以下のことです。

ここでは、以下のようなファイル構成でプロジェクトを作成しました。

環境構築

DBUnitを利用したテストを行うには、以下のJARをクラスパスに含めます。

テスト対象のクラスの作成

テストを行うには、何かの処理を行うクラスが必要です。ここでは、簡単ですが、以下のクラスを用意しました。ConnectionManagerはDBに接続してConnectionを取得するクラスと考えてください。

Sample.java(何かDB処理を行うクラス)

package hoge;

import java.sql.Connection;
import java.sql.Statement;

public class Sample {

    public void tatakiuri() throws Exception{
        
        Connection con = ConnectionManager.getConnection();
        Statement smt = con.createStatement();
        smt.execute("update cd set price=10");
        //con.commit();//autocommit
        con.close();
    }
}

テストケースの作成

このクラスに対する単体テストを記述します。次のようになります。

SampleTest.java(テストケースの例)

package hoge;

import junit.framework.TestCase;

import org.dbunit.Assertion;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;

public class SampleTest extends TestCase {

    public void testTatakiuri() throws Exception {

        //準備データをDBに入れる
        DatabaseOperation.CLEAN_INSERT.execute(getConnection(),
                new FlatXmlDataSet(SampleTest.class
                        .getResourceAsStream("prepare.xml")));

        //ロジックの実行
        Sample sample = new Sample();
        sample.tatakiuri();

        //DBから実際のデータの取得
        IDataSet databaseDataSet = getConnection().createDataSet();
        ITable actualTable = databaseDataSet.getTable("cd");
        
        //期待値データの取得
        IDataSet expectedDataSet = new FlatXmlDataSet(SampleTest.class
                .getResourceAsStream("expected.xml"));
        ITable expectedTable = expectedDataSet.getTable("cd");

        //期待値と実際のデータの比較
        Assertion.assertEquals(expectedTable, actualTable);
    }

    protected IDatabaseConnection getConnection() throws Exception {
        return new DatabaseConnection(ConnectionManager.getConnection());
    }

}

データの作成

次に準備データと期待値データを用意します。準備データは先ほどAntタスクでエクスポートしたXMLをそのまま利用します。期待値データは、準備データをコピーして値を書き換えます。

expected.xml(期待結果のデータ)

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <cd TITLE="いかだに乗ってどこまでも" PRICE="10" DATE="2005-12-10"/>
  <cd TITLE="レレレ" PRICE="10" DATE="2004-10-10"/>
</dataset>

これでJUnitテストを実行すると、テストが実行できます。ここではテストメソッド内でデータ準備を行っていますが、setUp()で行ってもいいでしょう。また、DBUnitのWebページのチュートリアル(Qucik Start)では、TestCaseでなく、DatabaseTestCaseを継承した例が示されています。ここでは更新系のテストなので、期待値データを準備しましたが、照会系であれば、いらないでしょう。また、テストデータをクラスパス(ソースパス)に含めていますが、別のフォルダに置いてもいいでしょう。テストのやり方はいろいろ考えられます。

その他

EXCELでテストデータを記述

DBUnitのテスト用データは基本はXMLですが、元々2次元のテーブルデータであるわけで、やっぱりEXCELなどでデータは編集した方がやりやすいです。DBUnitでは、EXCELデータも読み込めるようになっています。

まず、DBからのエクスポートですが、Antタスクはない(マニュアル上は)ようなので、とりあえずコードを書いてエクスポートします。といっても2、3行のコードです。

XLSExporter.java(DBデータをEXCELにエクスポート)

package hoge;

import java.io.FileOutputStream;

import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;

public class XLSExporter {

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

        DatabaseConnection con = new DatabaseConnection(ConnectionManager
                .getConnection());
        IDataSet dataset = con.createDataSet();
        XlsDataSet.write(dataset, new FileOutputStream("export.xls"));

    }
}

出力されたファイルは次のようになっています。これを元に、準備データや期待値データを作成します。

で、テストケース上では、FlatXmlDataSetを利用していたところを、XlsDataSetに変えればEXCELデータを読み込んでのテストが行えます。

なお、XlsDataSetは内部でJakarta POI(http://jakarta.apache.org/poi/)を利用しているので、上記を実行するには、POIをダウンロードしてきて、JARをクラスパスに含める必要があります。一応最新リリースの2.5.1では動きました。

Pros & Cons

と、まあ、DBUnitを非常に簡単に利用してみました。私自身が全然使いこなしてないので、なんちゃってですが、以下感想です。

DBの準備、期待値確認が簡単

データ作成は若干面倒ですが、それを考慮してもDBの中身の期待値確認が非常に簡単です。これをイチイチ、テストケース中にSQL書いて中身を確認するとぞっとしますよね。もちろん条件式を書いてのデータセットの取得なども可能です。

スキーマが変わったとき辛そう

JUnitでは、インターフェイスやロジックなどの変更があったとき、テストケースも合わせて直さないといけないので、ころころ仕様変更がある場合などは、次第にテストを書くのがだるくなってきます(とは言っても、テストを書くメリットの方が大きいから単体テストを書くわけですが)。DBUnitの場合、DBのスキーマ変更があった場合、テストデータの作りなおしになり、これがJUnitにおけるインターフェイス変更より辛そうな感じです。DBスキーマ含めたリファクタリングツールでもあるといいのにね(?)。

どこまで書くか?

JUnitテストでイチイチセッタ、ゲッタのテストを書かないように、DBUnitを使った場合も、イチイチDAOのテストを全部書く必要があるのかはまだ見えません。また、O/Rマッパーなどを利用する場合は、より書く場面も減ってくる気がします(自動生成されたコードに対してテスト書く?あるいはテストも自動生成すればいいのか?)。

参考

今回作ったサンプル
dbunit1.zip

JavaPress vol.34 Eclipseによるデータベースプログラミング DbEdit+XMLBuddyプラグインでDbUnitを利用する
http://www.gihyo.co.jp/magazines/javapress/contents/Vol34

[それはBooks]DBUnitでデータベーステスト
http://xlegend.dip.jp/~hamasyou/archives/Engineer-Soul/dbunitcuedbeditxmlbuddy.php


Top