2013年8月2日金曜日

データベースのテスト支援ツール DbUnit その3 DB参照編

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

現在はデータベースのテスト支援ツール「DbUnit」の連載中です。

前回の記事では、DbUnitとは以下の作業を簡単にやってくれるライブラリであるとご紹介しました。

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

前回のテストデータ登録編は「テーブルのセットアップ」に相当する内容でしたので、今回のDB参照編は「テーブルの中身のチェック」について執筆したいと思います。

DB参照機能の利用シチュエーションとしては、例えば「レコードの登録(insert)」を行う時です。

「テーブルにレコードを登録した結果、テーブルの中身が想定した内容になっていること」

この機能、自分で作る場合は自分でselect文を書かねばならず大変面倒ですが、DbUnitを使えば簡単に実現することが可能です。

処理の流れとしては以下になります。

  1. 現在入っているレコードを全部クリア
  2. レコードを登録
  3. テーブルの中身をチェック

赤文字の部分はDbUnitの機能がやってくれますので、開発者は自分の作ったソースである「レコードの登録」の部分だけテストコードを書けば良いというわけです。

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


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

商品テーブルにレコードを一件登録する「insert」のテストソースは以下になります。

public class ShohinServiceTest {

 /** コネクション */
 private Connection conn = null;
 /** DbUnit専用 */
 private IDatabaseConnection connection = null;

 /**
  * 最初にコネクションを接続する。
  *
  * @throws java.lang.Exception
  */
 @Before
 public void setUp() throws Exception {
  //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/entity/商品登録テストの予想結果.xml"));
  //テーブルをクリア
  DatabaseOperation.DELETE_ALL.execute(connection, dataset);
 }

 /**
  * コネクションをクローズして終了
  *
  * @throws Exception
  */
 @After
 public void tearDown() throws Exception {
  if(connection != null){
   connection.close();
  }
  if(conn != null){
   conn.close();
  }
 }

 /**
  * {@link jp.co.net.genesis.service.ShohinService#insert()} のためのテスト・メソッド。
  */
 @Test
 public void testInsert() throws Exception {

  ShohinService shohinService = new ShohinService();

  Shohin shohin = new Shohin();
  shohin.setShohinName("ジェニシス商品");
  shohin.setPrice(100);

  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
  shohin.setCreateAt(format.parse("2013-07-31"));
  shohin.setUpdateAt(format.parse("2013-08-01"));

  //レコードを登録
  shohinService.insert(shohin);

  //レコードを登録した結果の、レコード構成の予測値
  IDataSet expectedDataset = new FlatXmlDataSetBuilder().build(
                     new File("src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
  ITable expected = expectedDataset.getTable("shohin");

  //実際のテーブルのレコード構成
  ITable actual = connection.createDataSet().getTable("shohin");
  actual = DefaultColumnFilter.excludedColumnsTable(actual, new String[] { "id" });

  Assertion.assertEquals(expected, actual);

 }

}

量が多いですので、分解してご説明します。

コネクションの取得とテーブルのクリア


DBに接続して「IDatabaseConnection」を取得する部分については、前回と同じです。
前回はセットアップ時に1回接続するのみでOKでしたので、コネクションは使用したらすぐにクローズしていました。
しかし今回は「セットアップ」「チェック」の2回コネクションが必要となりますので、コネクションはグローバル変数に持つことにしました。
そして処理終了後の「tearDown」でクローズしています。

「setUpは各処理の最初に呼ばれる」
「tearDownは各処理の終了時に呼ばれる」

これはJUnitの中でも基本的で有名な部分ですので、あえて特筆する必要も無いでしょう。

今回の記事で特筆すべきは、以下の部分です。

IDataSet dataset = new FlatXmlDataSetBuilder().build(new FileInputStream(
       "src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
     //テーブルをクリア
     DatabaseOperation.DELETE_ALL.execute(connection, dataset);

前回の記事でも似たようなコードがありましたが、
前回との違いは処理モードが「DELETE_ALL」になっている点です。

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

前回も掲載したこの表にありますように、セットアップ時には複数の処理モードを選択することが出来ます。
今回はレコードの登録テストである都合上、テーブルの初期状態は空っぽである必要があるため、DELETE_ALLを選択しました。

「商品登録テストの予想結果.xml」の中に定義されているテーブルから全レコードを削除します。
もちろん、このxmlの中には「shohin」が定義されていますので、商品テーブルが全抹消となるわけです。

これでテーブルが綺麗になりました。

レコードの登録

レコードの登録は「そういう機能を自分で作っているだけ」ですので、DbUnitとして特記すべき機能はありません。
Shohin shohin = new Shohin();
 shohin.setShohinName("ジェニシス商品");
 shohin.setPrice(100);
 
 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
 shohin.setCreateAt(format.parse("2013-07-31"));
 shohin.setUpdateAt(format.parse("2013-08-01"));
 
 //レコードを登録
 shohinService.insert(shohin);

しかし、ここで予め断っておかなければならない点は「IDはセットしていない」ということです。
IDはserialによる自動インクリメントのパラメータですので、「insertメソッド」にはIDをセットする機能は入っていないのです。
つまり、IDにどんな値が入るかはその時の環境次第。
テストコードで制御出来ない厄介なパラメータということになります。

テーブルのチェック

最後にテーブルのチェックです。果たして「insert」は正常に機能してくれたでしょうか?

//レコードを登録した結果の、レコード構成の予測値
 IDataSet expectedDataset = new FlatXmlDataSetBuilder().build(
             new File("src/test/resource/jp/co/net/genesis/entity/商品登録テストの予想結果.xml"));
 ITable expected = expectedDataset.getTable("shohin");

 //実際のテーブルのレコード構成
 ITable actual = connection.createDataSet().getTable("shohin");
 actual = DefaultColumnFilter.excludedColumnsTable(actual, new String[] { "id" });

さて、ここでまたxmlファイル「商品登録テストの予想結果.xml」が登場していますね。

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

<dataset>
 <shohin shohin_name="ジェニシス商品" price="100" create_at="2013-07-31"  update_at="2013-08-01" />
</dataset>

ただテーブルのレコード構成を書いておけば良い、というシンプルで分かり易い仕様です。
しかし、ここで一つ問題が。
「ID」が入っていません。

つまり、「ID」にどんな値が入ってくるか分からないため、IDはスルーしているわけです。
このような特定カラムのスルーは、Javaのソース上で「DefaultColumnFilter.excludedColumnsTable」を使ってスルー対象カラムを定義し、その上でxmlファイルに記述しなければOKです。
これで無事にテーブルの照合が出来ました。

終わりに


これでDbUnitの基本的な使い方は終了です。
独自のクラス、設定、xmlなどを使うので最初は大変ですが、慣れてしまえば簡単なものです。

実際の開発では、DbUnit周りは別途フレームワーク化して、DatabaseConnectionやIDataSetなどゴチャゴチャしている所はフレームワークに預けてしまう形を取るのが良いでしょう。

次回は応用編と題しまして、JUnit本体の機能とDbUnitを組み合わせた、テストケースの整理についてご紹介します。

0 件のコメント:

コメントを投稿