SeasarV2によるテスト機能Seaser Projectの全貌を探る(4)

Seasar(シーサー)は、国内のコミュニティ「The Seasar Project」によって開発が行われているオープンソースプロダクトだ。DI+AOPコンテナとして評価が高いSeasarV2は、J2EE開発の現場にも影響力を持ち始めた。例えば電通国際情報サービスがSeasar Projectを正式に支援することを表明し、2005年6月からは同社による商用サポートサービスが開始されている。本連載では、同プロジェクトの代表的なプロダクトを紹介していく。(編集局)

» 2005年09月30日 00時00分 公開
[沖林正紀@IT]

 SeasarV2(以下、S2)には、テストツールJUnit(http://junit.org/)を拡張したテストフレームワークが用意されています。これは「S2Unit」と名付けられています。JUnitを用いる場合はjunit.framework.TestCaseクラスを継承してテストプログラムを作成しますが、S2Unitの場合は、これのサブクラスであるorg.seasar.extension.unit.S2TestCaseクラスを継承してテストプログラムを作成します。

 S2でテストを行う利点は、アプリケーションサーバなど、S2以外の実行環境を用意しなくてもよいということでしょう。もちろんアプリケーションによってはJDBCドライバなどが必要になることはありますが、例えばEJBのようにアプリケーションサーバにデプロイするといった手続きは必要ありません。S2でのテストはテスト用のdiconファイルを用意するだけで済みます。そしてこの後すぐ紹介するように、テスト用のdiconファイルでアスペクトの機構を用いてモックオブジェクトを設定することができますので、テスト用のオブジェクトを特別に作成する必要がありません。また、データベースにアクセスするアプリケーションの場合でも、第3回で紹介したDataSetをデータベースに見立ててテストを行うことができるようになっています。

 以下にS2UnitにおけるJUnitから拡張した機能の一部を示します。詳細はSeasarプロジェクトのドキュメント(http://www.seasar.org/S2Unit.html)をご覧ください。

  • testXxxxxで定義されるテストメソッドに対し、その処理のための初期化を行うsetUpXxxxxメソッドや終了処理を行うtearDownXxxxxメソッドを定義できる
  • testXxxxxTxのように、テストメソッドの末尾にTxを付けると、自動的にこのメソッドの前後でトランザクション処理を行ってくれる
  • DataSetのデータとデータベースのデータとを比較することができる
  • テストの結果をExcelファイル、Map、Bean、SQLの実行結果と比較することができる

モックオブジェクトによるテスト

 S2には、MockInterceptorという、本来のオブジェクトをテスト用のモックとして扱うためのインターセプタが用意されています。これを用いたテストを行う方法を紹介しましょう。ここでは、第2回「DI+AOPを実現するSeasar V2」で登場した面積を求めるアプリケーションのモックをこのインターセプタを使ってテストしてみます。なお、今回に関連するソースコードはここからダウンロードできます。

 テストしたいアプリケーションに、その基となるインターフェイスが存在している場合は、そのインターフェイスさえあれば、S2がそれをモックオブジェクトにしてくれます。具体的には、diconファイルのコンポーネント定義の<aspect>〜</aspect>間でMockInterceptorのコンポーネント定義を書き込むだけです。

 モックオブジェクトを定義するのは、MockInterceptorのsetReturnValueメソッドで、どのメソッドからどの値を返すかをdiconファイルに直接記述します。テストするメソッドが複数存在する場合は、setReturnValueメソッドによる設定を複数記述してください。

   モックオブジェクトを作成する例
    (MockInterceptorの定義をアスペクト定義内に記述した場合)
<!-- このインターフェイスを持つモックオブジェクトを定義する -->
  <component class="myfirst.di.Area">
    <aspect>
      <component class="org.seasar.framework.
             aop.interceptors.MockInterceptor
">
        <initMethod name="setReturnValue"><!-- これを複数記述することもある -->
          <arg>"findArea"</arg><!-- 面積を求めるメソッド -->
          <arg>1.0</arg><!-- 面積は必ず1とする -->
        </initMethod>
      </component>
    </aspect>
  </component>

 このようにして定義したモックオブジェクトを用いてテストを行う例をリスト1、実行結果をその下に示します。テストはS2Unitのフレームワークにより自動的に行われますので、その流れを説明します。

  1. テストの初期化を行うsetUpメソッドでdiconファイルを読み込みます
  2. privateのクラス変数として定義されているAreaオブジェクトに、インスタンスが自動的に設定されます
  3. testAreaメソッドを実行します。ここではassertEqualsメソッドにより、AreaオブジェクトのfindAreaメソッドの値が1.0になるかを比較しています。diconファイルでfindAreaメソッドの値を1.0に設定しているため両者は同じ値となり、テストは成功します
リスト1 第2回「リスト1」のモックオブジェクトをテストする [AreaTest.java
package myfirst.test;

import org.seasar.extension.unit.S2TestCase;

import junit.textui.TestRunner;
// import junit.swingui.TestRunner;

import myfirst.di.*;

public class AreaTest extends S2TestCase  {

  private static String DICON_FILE = "s2test.dicon";

  private Area            area;

  public void testArea() throws Exception  {
    assertEquals( 1.0, area.findArea() );         // モックオブジェクトでテスト
  }

  protected void setUp() throws Exception  {  include( DICON_FILE );  }

  public AreaTest( String name ) {  super( name );  }

  public static void main( String[] args )  {
    TestRunner.run( AreaTest.class );
  }

}
  

   実行結果(テストが正常に終了した場合)

Time: 2.154

OK (1 test)


DBアクセスのテスト

 次に、データベースにアクセスするアプリケーションのテストをどのように行えばよいかを紹介します。この場合は、まずアプリケーションの実行後に予想されるデータベースの状態をあらかじめDataSetオブジェクトとして作成しておき、それと実際の結果とを比較します。

 このとき用いるDataSetオブジェクトはテストプログラムの中で直接データを設定しても良いですし、Excelファイルからインポートすることもできます。そうすれば、直接データベースを使用せずにデータベースへのアクセスのテストが可能になります。ここではDataSetオブジェクトに登録するデータを直接生成し、プログラムの実行結果と比較する方法を紹介します。

DataSetオブジェクトの生成

 まず、ここで使用するDataSetオブジェクトを生成してみます。生成するオブジェクトは、以下に示すcategoryテーブルと同じデータを持つDataTableオブジェクトです。このオブジェクトをDataSetに登録すると、DataSetをテスト用のデータベースとして扱うことができます。MySQLのデータベースに登録されているcategoryテーブルのデータと、これに準じたデータをDataSetオブジェクトに登録する例を以下に示します。

id name
1 食費
2 交通費
3 書籍代
4 光熱費
5 家賃
6 給料
categoryテーブルのデータを生成し、DataSetに登録する例
  public DataSet initDataSet() throws Exception  {

    DataSet s2Test = new org.seasar.extension.dataset.impl.DataSetImpl();

    DataTable category = s2Test.addTable( "category" ); // テーブル名の設定
    category.addColumn( "id" );                         // 列名の設定(id)
    category.addColumn( "name" );                       // 列名の設定(name)
    DataRow row1 = category.addRow();                   // 1行目の生成
    row1.setValue( "id", 1 );                           // 1行目のデータの設定
    row1.setValue( "name", "食費" );                    // 1行目のデータの設定
    DataRow row2 = category.addRow();                   // 2行目の生成
    row2.setValue( "id", 2 );
    row2.setValue( "name", "交通費" );
    DataRow row3 = category.addRow();                   // 3行目の生成
    row3.setValue( "id", 3 );
    row3.setValue( "name", "書籍代" );
    DataRow row4 = category.addRow();                   // 4行目の生成
    row4.setValue( "id", 4 );
    row4.setValue( "name", "光熱費" );
    DataRow row5 = category.addRow();                   // 5行目の生成
    row5.setValue( "id", 5 );
    row5.setValue( "name", "家賃" );
    DataRow row6 = category.addRow();                   // 6行目の生成
    row6.setValue( "id", 6 );
    row6.setValue( "name", "給料" );

    return s2Test;

  } 

Excelファイルとの比較

 まず、このDataSetオブジェクトの内容と、画面1のようなExcelファイルから読み込んだデータの内容とを比較してみます。読み込むために必要なデータの内容についてはSeasarプロジェクトのドキュメント(http://www.seasar.org/testtech.html#s2unitexcel)を参照してください。

画面1 テスト用に作成したExcelファイル(OpenOffice1.9.112で表示)※筆者はOpenOffice Calcで作成したExcelファイルによる動作確認はしていない 画面1 テスト用に作成したExcelファイル(OpenOffice1.9.112で表示)※筆者はOpenOffice Calcで作成したExcelファイルによる動作確認はしていない

 Excelファイルからデータを読み込むには、以下のように、S2TestCaseクラスのreadXlsメソッドを用います。このメソッドを実行するとDataSetを取得することができます。というのは、Excelファイルに複数のシートが作成されていると、そこから複数のDataTableオブジェクトを生成してDataSetオブジェクトに登録するためです。

      DataSet fromXls = readXls( "Excelファイル名" );

 両者のデータを比較するためには、DataSetを引数とするassertEqualsメソッドかDataTableを引数とする同メソッドを用いることができます。ここで用いるassertEqualsメソッドは、JUnitのそれとは異なり、S2Unitで新たに実装(拡張)されたものです。Excelファイルにcategoryテーブルのデータしか作成されていない場合はDataSetを引数にして比較させることができますが、Excelファイルに別のテーブルのデータも作成されている場合は、DataSetから個別のDataTableを取得して比較しなければなりません。

   DataSet同士を比較する場合
       assertEquals( fromXls, s2Test );

   DataTable同士を比較する場合
     assertEquals( fromXls.getTable( "category" ), s2Test.getTable( "category" ) );

MapList形式での結果との比較

 今度は、第3回の「MapListによるデータの取得」の項で紹介したMapListResultSetHandlerを用いて取得した結果との比較をしてみます。この場合は、まずこのコンポーネントを持つSelectHandlerオブジェクトをDIコンテナから取得しなくてはなりません。ここではS2TestCaseインスタンスが保持しているものをgetContainerメソッドにより取得しておいてから、目的とするSelectHandlerを取得します。

 DataSetオブジェクトのデータとSelectHandlerから取得したデータを比較するには、以下のように、assertMapListEqualsメソッドの第2引数、第3引数にそれぞれを設定し、実行します。MapListからDataSetへ、またはその逆への変換は必要ありません。

      SelectHandler handler = (SelectHandler)getContainer()
                              .getComponent( "selectCategoryMapList" );
    List catList = (List)handler.execute( null );
    assertMapListEquals( "MapListで比較", s2Test, catList );

SQL文の実行結果との比較

 さらに、このDataSetオブジェクトのデータとSQL文の実行結果を比較してみます。SQL文の実行結果はDataTableオブジェクトで取得するため、両者の比較には、DataTableを引数とするassertEqualsメソッドを用います。

 比較の対象とするSQL文の実行結果を取得するには、以下のように、S2TestCaseクラスのreadDbBySqlメソッドを実行します。第1引数がSQL文(SELECT)で第2引数がDataTableオブジェクトに付けるテーブル名です。

      DataTable category =
readDbBySql( "SELECT id, name FROM category ORDER BY id", "category" );

 最後に、先ほどデータを登録したDataSetと比較します。両者の内容が同じであれば、このテストは正常に終了します。

      assertEquals( category, s2Test.getTable( "category" ) );

 S2TestCaseには、これまで紹介した以外にも、例えばテーブル名からDataTableオブジェクトを生成するreadDbByTableメソッドやDataSetの内容をそのままデータベースに書き込むwriteDbメソッドなどのように便利なメソッドがいろいろと用意されていますので、興味がある方はソースコードを読んだり、APIドキュメントを生成したりして探してみてください。

 今回までで、Seasarプロジェクトの中核であるSeasarV2(S2)が持つ機能について、その一部を紹介してきました。DIコンテナ、AOP、DBアクセス、ユニットテストというように、S2だけでもさまざまな機能が用意されていることがお分かりいただけたでしょうか。もっと詳しく知りたいという方は、Seasarプロジェクトのドキュメントや、開発者のWebサイトなどをお読みいただき、さらに理解を深めていただければと思います。

 次回はO/RマッピングによるDBアクセスを行うS2Daoについて紹介していきます。引き続きどうぞよろしくお願いいたします。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。