2013年7月24日水曜日

データベースのテスト支援ツール DbUnit その2 テストデータ登録編

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

前回よりデータベースのテスト支援ツール「DbUnit」のご紹介をしております。

さて、前回の記事ではDbUnitの説明というより、JUnit開発というのは全般的に環境設定周りが面倒だ、というお話をしました。
今回はいよいよ、DbUnitの何が便利なのかをご紹介しましょう。
簡単に言えば、DbUnitとは以下の作業を簡単にやってくれるライブラリです。

  • テーブルのセットアップ
  • テーブルの中身のチェック

これだけです。
たったこれだけですが、これを自前の開発でやろうとすると実に大変な労力を要するもの。

例えば、「テーブルから主キーで1アイテムを検索する機能のテスト」を想定してみましょう。

テーブルの検索機能のテストである以上、テーブルの中身が前もって想定された状態になっていなければJUnitが正常に動作しません。
そこで、テストとしては以下の順番を辿ります。
  1. 現在入っているレコードを全部クリア
  2. そのテスト専用のレコードをセットアップ
  3. テスト実行
この「1」と「2」ですが、自分でSQL文を書いてこの機能を実現していたのでは非常に面倒ですし、そもそも「DBとの処理を行う機能のテストの為に、DBとの処理を行う機能を作る」では意味が分かりません。
新しくバグを埋め込むのがオチです。
しかし、DbUnitには「1」と「2」の機能が最初から入っています。
これにより、簡単で、高機能で、頑強なテストを行えるわけです。

今回はDbUnitの二大機能「セットアップ」「チェック」のうち、セットアップの方をご紹介します。

実例編(テストデータ登録)

では、実際にサンプルソースをお見せしましょう。

商品テーブルを主キー検索する「findById」のテストソースは以下になります。

public class ShohinServiceTest {

  /**
   * @throws java.lang.Exception
   */
  @Before
  public void setUp() throws Exception {
 Connection conn = null;
 IDatabaseConnection connection = null;
 try{
  //JDBCドライバーをセット
  Class.forName(GenesisConfig.JDBC_DRIBER);
  //Connectionの取得
  conn = DriverManager.getConnection(GenesisConfig.DB_URL,
    GenesisConfig.DB_USER, GenesisConfig.DB_PASSWORD);
  //IDatabaseConnectionの作成
  connection = new DatabaseConnection(conn);
  //データセットの取得
  IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
    "src/test/resource/jp/co/net/genesis/service/商品_登録数3.xml"));
  //セットアップ実行
  DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);
 }finally{
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }
  }

  /**
   * {@link jp.co.net.genesis.service.ShohinService#findById()} のためのテスト・メソッド。
   */
  @Test
  public void testFindById() throws Exception {
 ShohinService shohinService = new ShohinService();
 Shohin shohin = shohinService.findById(111);
 //エンティティのチェックにはMacherを使いましょう。
 assertThat(shohin,new ShohinMacher(shohin));
  }

}

重要点は沢山あるので、分解してご説明します。

コネクション取得

DbUnitはデータベースに接続するライブラリですので、
DbUnit専用に別途コネクションを取得する必要があります。
以下がその部分です。

public void setUp() throws Exception {
 Connection conn = null;
 IDatabaseConnection connection = null;
 try{
  //JDBCドライバーをセット
  Class.forName(GenesisConfig.JDBC_DRIBER);
  //Connectionの取得
  conn = DriverManager.getConnection(GenesisConfig.DB_URL,
    GenesisConfig.DB_USER, GenesisConfig.DB_PASSWORD);
  //IDatabaseConnectionの作成
  connection = new DatabaseConnection(conn);

                //省略

 }finally{
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }
  }

  • JDBCドライバーをセットする。(GenesisConfig.JDBC_DRIBER)
  • 接続先データベースをセットする。(GenesisConfig.DB_URL)
  • 接続ユーザをセットする。(GenesisConfig.DB_USER)
  • 接続パスワードをセットする。(GenesisConfig.DB_PASSWORD)
  • コネクションをセットする。

至ってシンプルな段取りを踏む作業です。
「IDatabaseConnection」というDbUnit独自のインターフェースが登場しています。
これによってDbUnitはデータベースに接続するわけですが、特記事項と言うべき程の事も無く、こういうものだとご理解頂ければ良いかと。

今回はソースを紹介する都合上、全ソースを「ShohinServiceTest」に記載していますが、
実際にはこのコネクション取得部分は別途ユーティリティクラスを作るか、スーパークラスを作ってライブラリ化することをオススメします。

データ定義

次にデータの登録です。
今回は主キー検索で1件を取得する機能のテストですので、テーブルには3件のレコードを入れておき、その中から狙ったレコードが取得出来ることを以てテストOKとするシナリオにしたいと思います。

そのうち、データを定義する部分が以下です。

//データセットの取得
 IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
     "src/test/resource/jp/co/net/genesis/service/商品_登録数3.xml"));

ファイル名から分かりますように、データの定義は別途xmlで定義します。
これをデータセットと言います。


ここで前回でご説明した「ソースフォルダの切り分け」が効いてきます。
xmlファイルは別にどこに置いても動作は可能えすが、テストリソースフォルダのパッケージの配下に置くことによって、そのxmlが何のデータなのか整理がつくわけです。

このxmlファイルの中身は以下です。

<dataset>
 <shohin id="111" shohin_name="テスト商品1-1-1" price="111" create_at="2013-01-20"  update_at="2013-01-17" />
 <shohin id="222" shohin_name="テスト商品2-2-2" price="222" create_at="2013-02-20"  update_at="2013-02-17" />
 <shohin id="333" shohin_name="テスト商品3-3-3" price="333" create_at="2013-03-22"  update_at="2013-02-23" />
</dataset>

つまり、以下のような構造になっていればOKというわけです。
<dataset>
   <テーブル名 カラム名="値" ……/>
   <テーブル名 カラム名="値" ……/>
</dataset>

見易くで綺麗なフォーマットですね。

備考

上記の例はxmlファイルですが、データセットはエクセルで用意することも可能です。
エクセルなら行を増やす時にエクセルの機能でカウントアップとかコピー&ペーストが出来て簡単とう便利な側面がある反面、ファイルを開くのが重くなったり、svn等での比較が難しかったり、ファイルサイズが大きくなったりするなどのデメリットもあります。
どちらを使うかは好みの問題と言えるでしょう。

登録実行

「データベースとのコネクションは取得した。」「登録するデータセットも作成した」
後はテーブルに流し込むだけです。

//セットアップ実行
 DatabaseOperation.CLEAN_INSERT.execute(connection, dataset);

DbUnitでは作成したデータ定義を流し込む際に、実行モードを指定することが出来ます。
それが以下です。

「DatabaseOperation.CLEAN_INSERT」

データ定義に記入されているテーブルの中身を全部クリアした後、新規で登録するというモードです。
登録モードには複数種類がありまして、以下がその一覧です。

パラメータ機能
NONEダミー。何もしない。
UPDATEデータセットで定義されているレコードを更新する。
INSERTデータセットで定義されているレコードを登録する。
REFRESHデータセットで定義されているレコードがテーブルに無ければ登録、あれば更新する。
DELETEデータセットで定義されているレコードをテーブルから削除。
DELETE_ALLデータセットで定義されているテーブルから全レコードを削除。
TRUNCATE_TABLEデータセットで定義されているテーブル自体を抹消。
CLEAN_INSERTDELETE_ALLしてからINSERT

色々な機能が揃っていますね。

しかし、私はこの中で使うのは「CLEAN_INSERT」「DELETE_ALL」の2つで十分というか、この2つ以外には使わないと縛った方が良いケースが殆どかと思います。
状況次第では「全部消してインサートだと処理が重くなるので、UPDATE等を使って必要な部分だけ差し替える」といった需要も出てくるかもしれません。
しかし、それだとテスト間の結合が密になってしまい、「片方のテストケースを直したら、別のテストケースが壊れた」という事態が発生する可能性が高まります。
「全部抹消」と「新規登録」。少々非効率であったとしても、この2パターンの組み合わせでテストケースを作っていくことが、堅牢なテストを行うには一番だと思います。

チェック

最後は普通にチェックを行うだけです。
「テーブルのセットアップ」と「チェック」を分けて定義することによりソースが綺麗になっています。

@Test
  public void testFindById() throws Exception {
 ShohinService shohinService = new ShohinService();
 Shohin shohin = shohinService.findById(111);
 //エンティティのチェックにはMacherを使いましょう。
 assertThat(shohin,new ShohinMacher(shohin));
  }

終わりに

今回は初めてのDbUnit使用でしたので内容が盛り沢山になってしまいました。
上の方では「DbUnitを使えば簡単にデータベースのテストが出来る」と書いてしまいましたが、それはある程度使い慣れて、フレームワークが整った後の話です。
最初の導入段階ではある程度の負荷が発生するかもしれません。

しかし、最初こそ大変かもしれませんが、使いこなせれば非常に便利なライブラリであることも確かです。
ぜひ使いこなして、充実したユニットテストを実現して下さい。

0 件のコメント:

コメントを投稿