2013年8月26日月曜日

最強モックツール JMockit その2 インストール編

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

現在はモックツール「JMockit」をご紹介しています。
今回はインストールと動作確認まで行ってみましょう。

ダウンロード&インストール

まずはJUnit本体のダウロードです。

何度も書いていますが、Eclipseの標準JUnitだと不足がありますので、自前でJUnitセットをダウンロードして下さい。


次にJMockitをダウンロードします。
この記事を執筆している現時点での最新は1.4です。


ダウンロードしたzipファイルを解凍すると「jmockit.jar」と「jmockit-coverage.jar」の二つのjarファイルがあります。
それぞれ、jmockit本体とカバレッジ出力ツールです。

今回の連載ではカバレッジも紹介しますので、両方とも導入しましょう。

ダウンロードしたら、これら全部をEclipseのクラスパスに入れて完了。
単にjarファイルを導入するだけの簡単な作業です。


では無いんですよね!!


何と、jmockitは「クラスパスをJUnit本体より先にしなければならない」という制約があります。

普通の開発ではクラスパスの順番など意識しないと思いますが、今回は違います。
Javaのプロジェクトに複数のjarファイルを導入する際、異なるjarファイル間で同じクラスが存在していた場合は、先に書いた方のjarファイルの方が優先されます。

つまり、jmockitはJUnit本体より先にクラスパスを書いて、JUnit本体の一部を潰して機能を実現しているわけです。
導入段階から早くも黒魔術です。

Eclipseの場合、プロジェクトのプロパティから「順序およびエクスポート」の画面に進むことでクラスパスの順番を変更出来ます。

私の環境では以下のような状態になりました。



これでセットアップ完了です。

動作確認

次に細かいことは抜きにして、まずは動作確認してみましょう。

本体ソースの方は、以下のようにただ「テスト」という文字列を返すだけのクラスとメソッドを作ります。

public class Sample01 {
 public String doTest() {
  return "テスト";
 }

}

これに対し、jmockitを使って「テスト」という返り値を「モック」に差し替えてみたクラスが以下です。
/**
 *
 */
package jp.co.net.genesis.sample;

import mockit.Mocked;
import mockit.NonStrictExpectations;

import org.junit.Test;

public class Sample01Test {

 /** モック  */
 @Mocked
 private Sample01 mockSample01;

 /**
  * {@link jp.co.net.genesis.sample.Sample01#doTest(java.lang.String[])} のためのテスト・メソッド。
  */
 @Test
 public void testDoTest() {

  new NonStrictExpectations() {{
   mockSample01.doTest(); result = "モック";
        }};

        System.out.println(mockSample01.doTest());

 }

}

ひとまず実行してみて下さい。
「モック」という文字になったかと思います。
これでjmockitの動作確認が出来ました。

上記ソースにありますように、モック化するクラスをフィールド変数に定義し、「@Mocked」というアサーションを付けるのがお約束です。
そして、具体的にどんな機能に差し替えるのかを書いているのが、「NonStrictExpectations」の内部の部分です。

意味は以後の連載でご説明するとして、今回の所は動きを確認出来ればOKでしょう。

カバレッジ


私の環境だと、Eclipseのコンソールには以下のように表示されます。


どうやらカバレッジも出力してくれたようですね。
導入段階で「jmockit-coverage.jar」を入れましたので、自動的にカバレッジまで出力してくれたようです。

さっそく見てみましょう。

まず、index.htmlを開くと、以下が表示されます。


どうやら全体のカバレッジ率を出してくれる画面のようです。
今回の場合「0%」になっていますが、これは本体ソースをモックで潰して実行してしまった為、本体ソースは全く実行していないからです。
このように、jmockitはちゃんと「本体ソースを通った箇所」「モックで潰した箇所」を別々にカウントしてくれますので、カバー漏れ等は発生しません。

クラス名をクリックすると、カバレッジ詳細が表示されます。


画像は赤一色になっていますが、通過した箇所としていない箇所をちゃんと別々の色で表示してくれますので、分かりやすいです。

これにてカバレッジの出力も確認出来ました。

注意点


ここで一つ注意を入れておきたいと思います。
jmockitの公式サイトを見ると、以下のような文章が。

HTML reports: a multi-page HTML report is written in the "coverage-report" directory, under the current working directory (a different output directory can be specified if needed). The directory is created if it doesn't yet exist; its contents are overwritten if previously generated. The report will include pages containing all Java source files covered by the test suite. By default, the tool looks for ".java" source files inside all directories of name "src" found directly or indirectly under the current working directory; any intermediate sub-directories between "src" and the top-level package directory, such as "src/java" for example, are also searched.

何と、カバレッジを出力する際は「フォルダ名」の指定があるようで、デフォルトでは「src」の配下がソース本体として扱うだそうです。

つまり、ソースフォルダの構成が影響します。

src
 L main
    Ljava
    Lresource 
 L test
    Ljava
    Lresource

みたいにsrc配下に「main」と「test」を置いてしまうと、testソースまでカバレッジ対象になってしまい、HTML出力にゴミが混ざってしまいます。

また別のパターンとして、

main
  Ljava
  Lresource 
test
  Ljava
  Lresource

みたいにsrcという名前のフォルダが無いと、カバレッジが出力されません。

では私はどうしたかと言うと、

src
  Ljava
  Lresource
test
  Ljava
  Lresource

こういう構成にしてみました。
これなら綺麗に出力出来るようです。

「カバレッジが出ないぞ!?」と困っている人は大概はコレだと思いますので、ご確認下さい。


終わりに


ひとまずこれでJMockitのインストールが出来ました。

次回からはJMockitの各種詳細機能の検証に進みます。

高機能なライブラリですので長くなると思いますが、最後までお付き合い下さい。

2013年8月17日土曜日

最強モックツール JMockit その1

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

今回から新シリーズ「モック編」の始まりです。

タイトルに「最強」などと派手な名前をつけてしまいましたが、実際に最強と呼び声の高い強力なJUnit支援ツールがあります。
それがモックツール「JMockit」です。
しかし、第一回である今回はJMockit個別の話の前に「モックとはなんぞや?」という所からご説明しましょう。

百聞は一見に如かず。まず、例をお見せします。


まず、以下のように「ジェニシス」という文字を返すだけの、シンプルなクラスを作ります。

public class Simple {
 public String getStr(){
  return "ジェニシス";
 }
}

これに対してテストケースを作り、System.out.printlnで出力結果を見てみます。
public class SimpleTest {
 @Test
 public void testGetStr() throws Exception {
  Simple simple = new Simple();
  System.out.println(simple.getStr());
 }
}

まあ、当然ながらコンソールには「ジェニシス」と出ます。当たり前です。

これに対し、モックを入れてみます。

public class SimpleTest {
 @Mocked
 private Simple mockSimple;
 @Test
 public void testGetStr() throws Exception {
  new NonStrictExpectations() {{
   mockSimple.getStr(); result = "モックになりました。";
        }};
  System.out.println(mockSimple.getStr());
 }
}

すると、コンソールには何故か「モックになりました。」と出てきてしまいました!?

不思議ですね。。。

本体ソースを見る限りはどうやったって「simple.getStr() == "ジェニシス"」以外にありえないのですが、
実際には違う値が出てきてしまいました。。。

このように「本体ソースの機能をダミーに差し替えて実行する」というハッキングみたいなことをする。
それがモックです。
テスト上の役割としては「スタブ」に近いものとなります。

定義の上では厳密言えば「スタブ」と「モック」は違うものなのですが、下位機能を本体とは別のソースで置換するという点は同じです。

本体ソースではテストが難しいケースに対し、ダミーの値に差し替えてテスト出来るというのがスタブの存在意義です。

モックの自作


上記のモックソースはJMockitを使った結果ですが、別にツールを使わなくても自分でモックすることは可能です。

public class SimpleTest {
 @Test
 public void testGetStr() throws Exception {
  Simple simple = simpleMock();
  System.out.println(simple.getStr());
 }
 private Simple simpleMock(){
  Simple mock = new Simple(){
         public String getStr(){
          return "モックになりました。";
         }
        };
        return mock;
 }
}

「Simpleを継承した別クラス」を作って、メソッドをオーバライドした上で差し替えてるわけです。
以下の部分がそれに該当します。

Simple mock = new Simple(){
  public String getStr(){
    return "モックになりました。";
  }

普通にクラスを継承して作るのでは無く、コードの中でクラスを作っています。
こういう書き方をしたクラスを「無名クラス」と言います。

今回私が作ったサンプルモックは低機能かつSimpleクラス専用ですが、JMockitを初めとした各種モックツールは、
これをどんなクラスにでも動的に対応してくれます。

動的対応には「リフレクション」という文字列からクラスを動的に生成する機能を使うのですが、
そこまで説明すると記事が長くなり過ぎるので割愛します。

それに、プライベートメソッドも容赦なく変えたり、クラスの中でnewしているクラスまで差し替えたりと何でもござれ。

でも、普通はこんなコーディングしませんよね。
このように、モックツールというのはJavaの中でも『黒魔術』とまで称される禁断のテクニックをフル活用した裏技ツールなのです。

モックツールの選別


さて、モックツールは世の中に沢山ありますので、どれを使うのが良いかという問題があります。
有名所と言えば以下辺りなんじゃないでしょうか?

  • EasyMock
  • Mockito
  • jMock

しかし、あえて私はタイトルにもありますように、JMockitを推したいと思います。
機能の豊富さが理由です。

以下のサイトをご覧下さい。


JMockitの公式サイトなので多少の贔屓が入っているのかもしれませんが、
やはり機能面では最強と見て間違い無いようです。

一方、「機能が多ければ使いこなすのも難しいのではないか?」という印象を受けるのも事実です。
しかし、逆に言えば使いこなせるだけの知識があれば無敵!!

ここは一つ、気合い入れて研究してみようではありませんか。

次回


次回から本格的にJMockitについて解説。
最初はインストール編です。

2013年8月12日月曜日

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

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

データベースのテスト支援ツール「DbUnit」の連載も今回で最終章です。

DbUnitの意義、基本的使い方につきましては前回まででご紹介済みです。
もう十分にDbUnitを有効活用していくことが出来るかと思います。

最終章の今回は、殆ど小ネタに過ぎない話ですが、デザインパターンについてご紹介します。

さて、DbUnitを用いたDB関連のテストは、概ね以下の構成を取ります。

  1. DBのセットアップ(テーブルのクリア、レコードの登録)
  2. テスト本体
  3. 終了処理(テーブルの掃除、セッションの切断)

ここで地味に問題になってくるのが、「DBのセットアップ」の処理の重さです。

「DBセットアップ=レコードのインサート」である為、この部分の処理に必要とする時間がオンメモリの処理と比べて重いのです。
一回一回のインサート処理ならばそんなに気にならない程度の重さですが、塵も積もれば山となる。
大きなプロジェクトで複数人が大量のDbUnitテストを作成した場合、段々段々と時間が掛かるようになり、仕舞いには一回のテスト実行で何時間も待たされたり、下手すれば一晩待っても終わらないです。
また、そんなに派手に遅くならない場合でも、ユニットテストは何度も繰り返すものですので、長い目で見れば馬鹿にならない時間を待ち時間に費やしていることになります。
これを「スローテスト問題」と言いまして、JUnit開発者の悩みの種です。

ユニットテストは、処理速度を意識しなければならない。

処理速度は、ユニットテストにおいて意外に大事な要素なのです。
今回のテーマはデザインパターンに加え、処理効率化についてもご紹介します。

デザインパターン


デザインパターンの要点は簡単です。
「Enclosedを使用し、DBのセットアップの種類に応じてグループ化すれば良い」です。

Enclosedについて忘れた方はこちら

/**
 * @author 技術開発事業部 遠藤 太志郎
 *
 */
@RunWith(Enclosed.class)
public class ShohinServiceTest {

 public static class テーブルが空の状態から始まるテスト {
  @Before
  public void setUp() throws Exception {
   //セットアップ処理(軽い)
  }
  @After
  public void tearDown() throws Exception {
   //終了処理
  }
  @Test
  public void testInsert() throws Exception {
   //テスト本体1
  }
  @Test
  public void testInsert2() throws Exception {
   //テスト本体2
  }
  @Test
  public void testInsert3() throws Exception {
   //テスト本体3
  }
  //以下続く……

 }

 public static class テーブルに100件のレコードが入っている状態から始まるテスト_select {
  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
   //セットアップ処理(重い)
  }
  @AfterClass
  public static void tearDownAfterClass() throws Exception {
   //終了処理
  }
  @Test
  public void testFindById() throws Exception {
   //テスト本体1
  }
  @Test
  public void testFindById2() throws Exception {
   //テスト本体2
  }
  @Test
  public void testFindById3() throws Exception {
   //テスト本体3
  }
  //以下続く……
 }

}

こんな感じです。
この要に「テーブルの初期状態でグループ化」しておくと可読性が高まります。
DbUnitのデザインパターンとしては鉄板の構成と言えるでしょう。

そして、処理効率化については以下のテクニックを使用しています。
上のソース、実は二種類のセットアップ方法を使い分けています。

@Before
public void setUp() throws Exception {
 //セットアップ処理
}

@BeforeClass
public static void setUpBeforeClass() throws Exception {
 //セットアップ処理
}

そう、「@Before」と「@BeforeClass」を使い分けているのです。
正確に言えば、「@After」「@AfterClass」も使い分けています。

このアサーションの意味は以下です。

アサーション意味
@Before各テストメソッドの実行前に毎回呼び出される。
@BeforeClass各テストクラスの実行前に一回だけ呼び出される。
@Afterデータ各テストメソッドの実行後に毎回呼び出される。
@AfterClass各テストクラスの実行後に一回だけ呼び出される。

つまり、

  • 「select系のテスト」のようにテストによってDBの中身が変わらない場合は「@BeforeClass」を使う。
  • 「insert、update、deleteのテスト」のようにテストによってDBの中身が変わる場合は「@Before」を使う。

こうすることによって、「select系のテスト」におけるセットアップ処理の実行回数を減らしているわけですね。
「insert、update、deleteのテスト」は高速化出来ませんが、これは仕方ありません。

しかし、多くのシステムの場合、「selecte系テストの機能の数 > 他の機能の数」という構成の事が多いですので、
select系のテストだけ高速化しても結構な成果を上げることが出来るのです。

注意事項


しかし、これだけは明言しておかなければいけません。

「@BeforeClassはなるべく使うな」

こんな記事を書いておいて何だと思われるかもしれませんが、これは大事な事です。
JUnitのテストは「各テスト間で干渉しない」ということが絶対必須。
メソッド毎の独立性は高ければ高いほど良いのです。

このため、メソッド毎に初期化を行う「@Before」は独立性が高いのに対し、
クラスで一回しか初期化しない「@BeforeClass」は独立性が低いと言えるでしょう。

「@BeforeClass」はテストの品質を下げる諸刃の刃なのです。

この為、

  • 「どうしてもこれらのテスト群は処理が重いから@BeforeClassで高速化しよう」
  • 「こっちのテスト群はそこまでの重さではないから、@Beforeにしておこう」

と言うように、@BeforeClassは最終手段の特殊対応。
基本は@Beforeを使うように心がけて下さい。

終わりに


以上でDbUnitのご紹介は終わりです。

今までの記事ではJUnitの機能をオンメモリで使う例を挙げて来ました
しかし今の時代、大抵は「DBの登録、更新、削除、検索」がシステムの主な機能ということが殆ど。
オンメモリで終わるシステムなんぞ滅多に無いでしょう。
このDbUnitの使い方をマスターした段階で、ようやく現場で使えるレベルに入ったといった所かと思います。

次回からは新章、「モック編」です。
JUnit界最強と名高いモックツール「JMockit」についてご紹介します。

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を組み合わせた、テストケースの整理についてご紹介します。

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のようにガチガチにせず、これくらいの緩い制約でクラス設計するのが開発しやすいのではないかと思います。

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