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を使えば簡単にデータベースのテストが出来る」と書いてしまいましたが、それはある程度使い慣れて、フレームワークが整った後の話です。
最初の導入段階ではある程度の負荷が発生するかもしれません。

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

2013年7月18日木曜日

データベースのテスト支援ツール DbUnit その1 インストール編

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

現在はテスト自動化シリーズと題しまして、JUnit関連のご紹介を執筆しています。
今回からはデータベースのテスト支援ツール「DbUnit」のご紹介になります。

さて、JUnitの開発で発生しがちなのが「環境依存」です。

  • あの時は正常に動いたのに、今は動かない。
  • Aさんが動かすと正常なのに、Bさんが動かすと動かない。

といったことが結構頻繁に起こりがちです。

原因としましては、

  • データベースが完璧にセットアップされている時だけ正常。ゴミレコードが入っているとNGになる。
  • AさんのPCには動作に必要なファイルが置いてある。BさんのPCには無い。(ソースを書いたのがAさんだから)

など、「動かなくて当たり前」な原因であることが殆どです。

前提条件となる環境が整備されていなくてはプログラムが動かないのは当たり前です。
しかし、その環境整備が結構面倒であるのも無視できない事実。
面倒だから後回しにしているうちに品質が低下していく、というのがJUnit開発の良くある風景です。

面倒な環境整備を簡単に済ませるのも技術力の一つ!!
というわけで、この連載では環境整備を簡単にするテクニックもテーマに掲げたいと思います。

しかし、環境整備と言っても条件は様々です。

  • データベースのレコードのセットアップ状態
  • 読み書きするファイルの状態
  • プログラムを実行するサーバの状態(読み取り権限、書き込み権限など)
  • 接続先サーバの状態(FTPサーバにファイルがある/無いなど)

色々あります。
それぞれ「スタブやモック」を使ったり「ダミーサーバ」を作ったりと色々なテクニックがあるのですが、今回からしばらくはこのうちの一つ、「データベースのセットアップ」並びに「登録後のレコードの照合」の作業を効率化する支援ツール「DbUnit」についてご紹介しようと思います。

プロジェクト構築

最初はプロジェクト構築からです。
以下の単位でソースフォルダを切ります。

ソースフォルダ役割
メインソーステスト対象となるメインのJavaソースを置く場所
メインリソースプロパティファイルなど、ソースから読み込みに使用する定義ファイルなどを置く場所
テストソースJUnitのソースを置き場所
テストリソースJUnitで使用する定義ファイルを置く場所


ここで早くも重要点ですが、プロジェクト開始時にこの粒度でソースフォルダを切って下さい。
もちろん、ソースフォルダなどどんな構成でもモジュールは動くわけですが、
プログラムの見通しを良くするという観点では、この粒度での切り分けが鉄板だと言い切ってしまって良いと思います。

ライブラリインポート


次にライブラリのインポートです。

以前の記事でご紹介したように、まずはJUnitの最新ライブラリを公式サイトよりダウンロードしてきます。
(Eclipseの自動機能でインポートすると、古いJUnitが入ってきたり、hamcrestが入ってなかったりするので、手動でインポートすることを忘れないで下さい)


次にDbUnitのライブラリをインポートします。
DbUnitは名前のとおり、JUnitを拡張したライブラリですので、JUnitとセットで使うことになります。
DbUnitの公式サイトからjarファイルをダウンロードし、インポートして下さい。


これにてライブラリのセットアップは完了、と思いきや、ここで罠があります。
このまま先に進んでしまうと、後で「java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory」とかエラーが出てきてしまいますのでご注意を。
内部で「slf4j」を使っているようですので、これもダウンロードしてインポートしましょう。



中には沢山のjarファイルが入っていますが、以下をインポートすればOKです。


  • slf4j-api-1.7.5.jar
  • slf4j-nop-1.7.5.jar


これにてjarファイルの準備完了。
最終的に、以下のようにjarファイルが揃っていればOKです。


DB準備

データベースのテストですので、当然ながらデータベース、テーブル、カラムが定義されており、そのテーブルに対し登録、更新、削除、参照の機能が作成済みの状態でなければ開始出来ません。

この記事では以下の環境でご紹介します。


  • PostgreSQL 9.1
  • SaStrutsやHibernateといったライブラリは使用せず、postgres標準ドライバのみで機能を作成済み


テーブルとしては簡単な「商品テーブル」を想定して以下を定義します。


物理名論理名
idIDserial(主キー)
shohin_name商品名text
price価格integer
create_at登録日時timestamp without time zone
update_at更新日時timestamp without time zone


  • IDは自動インクリメントするパラメータです。
  • 商品名と価格は特記事項の無し。
  • 登録日時、更新日時は、それぞれ「登録」「更新」を行った時に、その時の時間が入るという想定です。

終わりに

これにて準備は整いました。
実際にDbUnitを使ってのご紹介は次回からスタートします。

2013年7月14日日曜日

JavaScriptでstatic風なutilクラスを作成

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

業務多忙により更新が滞ってしまいましたが、また復活して更新を続けていきたいと思います。

前回から間が空いてしまいましたので、今回は単発の小ネタをご紹介しようと思います。
対象言語はJavaScriptです。

実は我々技術開発事業部では、もうすぐ社内企画としてとあるWebシステムを作ろうとしています。
Webシステムで必ず付いてくる機能と言えば、入力チェックです。

  • 必須項目が空欄だった場合、「○○を入力して下さい」とアラートを出す。
  • 半角数値項目に数値以外が入力されていた場合、「○○には半角数値を入力して下さい」とアラートを出す。
  • 日付項目に存在しない日付が入力されていた場合、「○○に正しい日付を入力して下さい」とアラートを出す。

よくある機能です。
こういった入力チェックはシステム全体で使い回しすることが多く、大抵の場合は「common.js」のような共通ファイルを作って、その中に以下のようなファンクションを作ったりします。

function isEmpty(str){
 return (str == null || str == "");
}

この例は「文字列の空白チェック」です。
他にも「isHankakuNum:半角数値チェック」「isDate:日付チェック」など入力チェックfunctionをズラッと作っていくことでライブラリ化するわけです。

しかし、こうやって単純にfunctionをバンバン作っていってしまいますと、名前空間が汚れてしまいます。
さらに言えば、そのfunctionがcommon.jsから呼び出したものなのか、画面個別に書いたものなのか、パッと見て分かりません。

つまるところ、機能としては出来ているのかもしれないが、イマイチ洗練されていないソースという状態なわけです。

そこで、もうちょっとスマートなやり方をご紹介しましょう。

まず、例としてJavaのケースを挙げてみます。
Javaでは「StringUtils」というライブラリが普及しておりまして、「StringUtils.isEmpry("aaa")」と記述してパラメータの空白チェックを行います。
「StringUtils」というクラスにパッケージされているのでメソッド名が被ったりしないですし、そもそも何のライブラリを呼び出しているのかも一目瞭然です。

これはStringUtilsクラスのisEmptyというメソッドがstaticなメソッドだから、このような記述方法が出来るわけです。
これと同じように、「JavaScriptでもstaticなメソッドを作ればスッキリする」というのが今回の記事の趣旨でございます。

JavaScriptの場合、以下のように書きます。

var StringUtils = new function(){
 this.isEmpty = function(str) {
  return (str==null || str=="");
 };
}

JavaScriptでは関数も変数の一つに過ぎません。
functionを「new」して作成し、StringUtilsという変数に代入することが出来ます。
(JavaScriptに「new」とかあるの?みたいな話も偶に聞きますが、普通にあります。)

これで「なんちゃってstaticメソッド」の完成です。
まあ、JavaScriptにstaticという概念はありませんので、プログラマーの使い勝手がstaticっぽいというだけですが。。。

これを共通JSファイルに書いておけば、後は「StringUtils.isEmpry("aaa")」と書いて空白チェックが行えるという寸法です。
ファイル名は「StringUtils.js」として保存しておけば、どこから呼び出されてきているのかも一目瞭然。
JavaScriptの可読性が少し上がりそうですね。

追伸

さらに小技ですが、型を意識するJavaならStringUtilsという名前のクラスで良いですけれども、型判定が大雑把なJavaScriptではそんなことをしても無駄に複雑化するだけ、という考え方もあります。

文字列チェック用にStringUtils、数値チェック用にIntegerUtils、日付チェック用にDateUtils、なんて細かく分けても面倒なだけです。

というわけで、私は「InputCheckUtils」として『入力チェック用』という機能単位で一括りにすることが多いです。
Javaのようにガチガチにせず、これくらいの緩い制約でクラス設計するのが開発しやすいのではないかと思います。

文字通り、型に囚われない自由自在なコーディングテクニックです。