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