|
コンポーネントのテスト
テスト容易性
コンポーネントベース開発の利点の一つとして、テストの容易性が挙げられています。特に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
Springの機能は用いませんので、JUnitの提供するTestCaseクラスを継承したテストクラスです(5行目)。実際のアプリケーションではSpringがインジェクション時に行う処理をsetUp()メソッドで行います(9行目のHelloServiceImplのインスタンス化と10行目のsetMessage()によるプロパティの設定)。14行目で新たに設けた引数なしのsayHello()メソッドを呼び出し、15行目で結果として"Hello, World"が返却されることをチェックするテストケースになっています。 ここまでテストコードを作成した状態でとりあえずコンパイルします。新たに設けたsayHello()メソッドはまだHelloServiceインタフェースには定義されていませんから、当然ながらコンパイルエラーになります。コンパイルエラーになることを確認したら、HelloServiceインタフェースに引数なしのsayHello()メソッドを追加します。同様にテスト対象のHelloServiceImplクラスにも引数なしのsayHello()メソッドを追加します。ただし、この時点ではメソッド内の処理は記述しません。
- example2/HelloService.java
- example2/HelloServiceImpl.java (メソッドの宣言のみ追加した状態)
10行目から12行目までが新たに追加した引数なしのsayHello()メソッドで、コンパイルが通るように仮でnullを返却するコードだけが記述されています。この状態で実行する(JUnitのTestRunnerやEclipseなどから実行できます)と、テストケースで指定した結果通りにはならず("Hello, World"が期待されているのに対し、nullが返却される)テストが失敗します。
- TestRunnerによる実行(失敗)
ここまででインタフェースを確定しましたので、実装していきます。
- example2/HelloServiceImpl.java (実装を追加)
- TestRunnerによる実行(成功)
テストケースが全て成功し、完成です。 以上のように、まずテストコードを書くことによってインタフェース(シグネチャ)を確定すると共に、期待する結果、すなわちそのクラス、メソッドの仕様(責務)を明確にします。テスト対象となる実装クラスはその仕様を満たすコードを書けばよく、インタフェースと実装を明確に分離することができます。 コンポーネントの粒度
四回にわたってSpringを用いたWebアプリケーションの例を取り上げてきました。最後に実際のアプリケーション開発におけるコンポーネントの粒度について触れておきます。 コンポーネントの粒度を決定するのは非常に難しく、明確な指標はありません。コンポーネントベース開発(CBD)ではコンポーネントの再利用がひとつの目標になるため、汎用性と柔軟性の高いインタフェースを持つことが重要になります。この連載でも限られた用途に作られたコンポーネントに対して汎用性と柔軟性を高める変更を加えていきました。ただし、一般的に汎用性や柔軟性と簡易性は相反する関係にあり、過度の汎用性はかえって利用しづらいものになりがちです。コンポーネントのインタフェースを決定(設計)する際には、実際に利用するアプリケーションを想定する(場合によっては実際に作ってみる)ことが重要です。また、一度決定したインタフェースの汎用性を後々高めることは比較的容易なのに対し、下げることは難しいといわれています。汎用性を下げるとは、新たな制約を設けて利用範囲を狭めることであり、既に利用しているものを排除することに繋がるためです。これを踏まえ、最初から汎用性を重視したインタフェースとするよりは、明確な利用方法に基づいたインタフェースからスタートすることが望ましいと考えられます。 また、Spring上のコンポーネント(Bean)はJavaのインタフェースクラスに基づいており、コンポーネントがかなり小さな単位になってしまいます。Javaのパッケージをひとつの論理的なコンポーネントの単位とみなして、利便性を高めることも有効です
|


牧野隆志(まきのたかし)




