CBDスクエア
テーマ:Javaコンポーネント技術解説:Spring  他の回へ
Spring 実践編:第4回
(発行日付: 2007/3/26)
コンポーネントのテスト
テスト容易性

コンポーネントベース開発の利点の一つとして、テストの容易性が挙げられています。特にSpringなどのDIxAOPコンテナを用いた場合は、コンポーネントがPOJOであるため、EJBのようにテストのためにもEJBコンテナが必要などということはありません。使い慣れたJUnitなどの単体テストツールを用いてコンポーネントのテストをすることができます。さらにテスト対象のコンポーネントが使用している他のコンポーネントがまだ存在しない(同時並行で開発中である)ような場合であっても、DIxAOPコンテナによるインジェクションの対象であればモックオブジェクトをインジェクションすることによって目的のテストを進めることができます。特に本物のコンポーネントでは発生させづらい状況(例えば任意の時点でのデータベースアクセスエラーなど)をモックオブジェクトでは容易に作り出すことができ、品質の向上にも寄与するといえるでしょう。

テスト駆動開発

DIxAOPコンテナを用いたコンポーネント開発においては、TDD(Test-Driven Development、テスト駆動開発)によるコンポーネントの実装が有効です。TDDは、ます最初にモジュール(CBDの場合はコンポーネント)のインタフェースを決めて、それを確認するテストコード書きます。実装コードはテストを満たすように書き、さらにリファクタリングによってより洗練された設計、実装コードにしていく開発方法です。コンポーネントのインタフェース(シグネチャ、責務)を明確にするとともに、テストしやすいシンプルな実装になり、結果としてコンポーネント間の依存度が弱くなります。

TDDでは容易にテストコードが書けることと容易に(繰り返し)テストが実行できることが重要ですが、Springはさらにテストを容易にする便利な機能を提供しています。具体的には、J2EEコンテナが提供しているJNDIなどのサービスをJ2EEコンテナに変わってテスト対象クラスに提供(インジェクション)する機能やデータベースへのアクセスを伴うテストに便利(トランザクションの制御など)なクラスです。

JUnitを用いた単体テスト

それでは実際にJUnitによる単体テストの例を見てみましょう。ここでは前回までに作成したHelloServiceクラスに新たな機能を追加していきます。HelloServiceクラスの実装クラスHelloServiceImplのsayHello()メソッドは、引数で指定された文字列に対し"Hello, "を付加して返却する仕様でした。新たに引数なしで呼び出された場合(引数なしのsayHello()メソッドを追加します)、デフォルトで"World"が指定されたものとして動作する機能を追加していきます。

まず、テスト駆動開発では単体テストコードを作成します。この連載の第一回のバッチアプリケーションや第二回のWebアプリケーションではSpringのDIを用いてHelloServiceImplのインスタンスをインジェクションしていましたが、HelloServiceImplはデータベースへのアクセスなどは行いませんので、(Springは用いず)テストクラス内で単純にnewでインスタンス化します。

- example2/HelloServiceImplTest.java
  1| package example2;
  2| 
  3| import junit.framework.TestCase;
  4| 
  5| public class HelloServiceImplTest extends TestCase {
  6|   private HelloService service;
  7|   public void setUp() throws Exception {
  8|     super.setUp();
  9|     service = new HelloServiceImpl();
 10|     service.setMessage("Hello, ");
 11|   }
 12| 
 13|   public void testSayHelloWithoutTarget() throws Exception {
 14|     String message = service.sayHello();
 15|     assertEquals("Hello, World", message);
 16|   }
 17| }

Springの機能は用いませんので、JUnitの提供するTestCaseクラスを継承したテストクラスです(5行目)。実際のアプリケーションではSpringがインジェクション時に行う処理をsetUp()メソッドで行います(9行目のHelloServiceImplのインスタンス化と10行目のsetMessage()によるプロパティの設定)。14行目で新たに設けた引数なしのsayHello()メソッドを呼び出し、15行目で結果として"Hello, World"が返却されることをチェックするテストケースになっています。

ここまでテストコードを作成した状態でとりあえずコンパイルします。新たに設けたsayHello()メソッドはまだHelloServiceインタフェースには定義されていませんから、当然ながらコンパイルエラーになります。コンパイルエラーになることを確認したら、HelloServiceインタフェースに引数なしのsayHello()メソッドを追加します。同様にテスト対象のHelloServiceImplクラスにも引数なしのsayHello()メソッドを追加します。ただし、この時点ではメソッド内の処理は記述しません。

- example2/HelloService.java
  1| package example2;
  2| public interface HelloService {
  3|   public void setMessage(String message);
  4|   public String sayHello(String target);
  5|   public String sayHello();
  6| }

- example2/HelloServiceImpl.java (メソッドの宣言のみ追加した状態)
  1| package example2
  2| public class HelloServiceImpl implements HelloService {
  3|   private String message;
  4|   public void setMessage(String message) {
  5|     this.message = message;
  6|   }
  7|   public String sayHello(String target) {
  8|     return (message + target);
  9|   }
 10|   public String sayHello() {
 11|     return null;
 12|   }
 13| }

10行目から12行目までが新たに追加した引数なしのsayHello()メソッドで、コンパイルが通るように仮でnullを返却するコードだけが記述されています。この状態で実行する(JUnitのTestRunnerやEclipseなどから実行できます)と、テストケースで指定した結果通りにはならず("Hello, World"が期待されているのに対し、nullが返却される)テストが失敗します。

- TestRunnerによる実行(失敗)
  1| $ java -classpath ".;junit.jar" 
                    junit.textui.TestRunner example2.HelloServiceImplTest
  2| .F
  3| Time: 0
  4| There was 1 failure:
  5| 1) testSayHelloWithoutTarget(example5.HelloServiceImplTest)
      junit.framework.ComparisonFailure: expected:<Hello, World> but was:<null>
  6|	at example2.HelloServiceImplTest.testSayHelloWithoutTarget
                                            (HelloServiceImplTest.java:15)
  7| 	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  8| 	at sun.reflect.NativeMethodAccessorImpl.invoke
                                        (NativeMethodAccessorImpl.java:39)
  9| 	at sun.reflect.DelegatingMethodAccessorImpl.invoke
                                    (DelegatingMethodAccessorImpl.java:25)
 10| 
 11| FAILURES!!!
 12| Tests run: 1,  Failures: 1,  Errors: 0

ここまででインタフェースを確定しましたので、実装していきます。

- example2/HelloServiceImpl.java (実装を追加)
  1| package example2
  2| public class HelloServiceImpl implements HelloService {
  3|   private String message;
  4|   public void setMessage(String message) {
  5|     this.message = message;
  6|   }
  7|   public String sayHello(String target) {
  8|     return (message + target);
  9|   }
 10|   public String sayHello() {
 11|     return sayHello("World");
 12|   }
 13| }

- TestRunnerによる実行(成功)
  1| $ java -classpath ".;junit.jar" junit.textui.TestRunner
                                           example2.HelloServiceImplTest
  2| .
  3| Time: 0
  4| 
  5| OK (1 test)

テストケースが全て成功し、完成です。

以上のように、まずテストコードを書くことによってインタフェース(シグネチャ)を確定すると共に、期待する結果、すなわちそのクラス、メソッドの仕様(責務)を明確にします。テスト対象となる実装クラスはその仕様を満たすコードを書けばよく、インタフェースと実装を明確に分離することができます。

コンポーネントの粒度

四回にわたってSpringを用いたWebアプリケーションの例を取り上げてきました。最後に実際のアプリケーション開発におけるコンポーネントの粒度について触れておきます。

コンポーネントの粒度を決定するのは非常に難しく、明確な指標はありません。コンポーネントベース開発(CBD)ではコンポーネントの再利用がひとつの目標になるため、汎用性と柔軟性の高いインタフェースを持つことが重要になります。この連載でも限られた用途に作られたコンポーネントに対して汎用性と柔軟性を高める変更を加えていきました。ただし、一般的に汎用性や柔軟性と簡易性は相反する関係にあり、過度の汎用性はかえって利用しづらいものになりがちです。コンポーネントのインタフェースを決定(設計)する際には、実際に利用するアプリケーションを想定する(場合によっては実際に作ってみる)ことが重要です。また、一度決定したインタフェースの汎用性を後々高めることは比較的容易なのに対し、下げることは難しいといわれています。汎用性を下げるとは、新たな制約を設けて利用範囲を狭めることであり、既に利用しているものを排除することに繋がるためです。これを踏まえ、最初から汎用性を重視したインタフェースとするよりは、明確な利用方法に基づいたインタフェースからスタートすることが望ましいと考えられます。

また、Spring上のコンポーネント(Bean)はJavaのインタフェースクラスに基づいており、コンポーネントがかなり小さな単位になってしまいます。Javaのパッケージをひとつの論理的なコンポーネントの単位とみなして、利便性を高めることも有効です


牧野隆志(まきのたかし)
株式会社アットウェア 代表取締役


長年、メーカー系SIerで学術研究機関のシステム開発に従事。
当時としては実績の少なかったオープン系RDBの大規模オンラインシステムへの適用や、全文検索エンジンの開発など技術的難易度が高いプロジェクトを経験。
2004年に「システムで人々を幸せにする」をテーマに同じ志をもった8人のメンバーでアットウェア設立、代表取締役に就任し現在に至る。
現在の個人的なテーマは、会社としていかにおもしろい仕事ができるか。技術者は仕事を楽しんでやらないとよいモノは作れないと信じている。
アジャイル・プロセス協議会などでも活動中。

株式会社アットウェア

2004年12月設立。
長年培ってきた「JavaEE」、「オブジェクト指向設計」、「SOA」の技術にて、システム構築をサポート。
特にフレームワークなどの基盤技術を得意とし、プレセールスからコンサル、開発、営業支援までを「ワンストップ・ソリューション」で提供する。
オープンな勉強会「atWorks」を定期的に開催。講師を招いての講演会という枠を超え、あるテーマに関しての全員参加による討論、それぞれ異なる経験、立場に基づいた人々の意見交換、および、ステップアップした仕事を実践するための研究会の場を提供。
UMLモデリングツール「JUDE」コンサルティングパートナーおよびメンテナンス情報ステーション「aftama」販売代理店。
 
InfoQ Japan 運営スタッフ募集
コンポーネントスクエアにて、IT技術情報サイト「InfoQ Japan」を立ち上げ半年が経ちました。おかげさまでアクセス数も順調に伸びてきております。
それに伴い運営業務が増加してきたため、「InfoQ Japan」の運営スタッフを募集いたします。 興味のある方はお気軽にお問い合わせ、ご応募ください。
応募要綱はこちら
協力企業
CBDスクエアは、次の各社様にご協力いただいております。
CBDスクエアとは

CBDスクエアは、コンポーネントベース開発に取り組む方々のコミュニティです。コンポーネントベース開発に取り組む方々に有用な情報の提供や情報交換の場の提供を行い、コンポーネントベース開発を推進してまいります。

CBDスクエアのすべてのコンテンツをご利用いただくには、CBDスクエア登録が必要となります。CBDスクエア登録をしていただくと、最新情報をメルマガとして配信いたしますので、ぜひ、ご登録ください。


コンテンツの詳細は、こちらをご覧ください。

CBDスクエア登録の手順や利用方法は、こちらをご覧ください。

CBDスクエアの登録を解除する場合は、こちらをクリックしてください。

不明点や何かご質問がありましたら、こちらからお問合せください。

InfoQ Japan 新着情報
この情報は、InfoQ Japanの新着情報です。InfoQ Japanはコンポーネントスクエアが運営する最新技術の情報コミュニティです。
Java最新ニュース
この情報は、japan.internet.com様からご提供いただいております。
おすすめサイトのご案内

(c) ComponentSquare, Inc. 2005-2006 All rights reserved. 会社情報お問い合わせ個人情報保護に関して