筆者はさまざまな開発現場で仕事をしていますが、テスティングフレームワークを活用しているプロジェクトは少ないように感じます。理由はさまざまですが、テストコード自体が書きづらいということも理由として挙げられるようです。この記事でお伝えしている筆者の現在のプロジェクトでは、DIを使うことでテストコードが書きやすくなったため、単体テストを実施しやすくなり開発効率を向上することができました。
テストコードが書きづらい理由の1つに、“依存性の問題”があります。つまり、1つのテストコードを記述するには、依存するクラスまで含めて検証しなければならないのです。
このため依存するクラスにバグや未完成な部分があるとテストができませんし、依存するクラスの変更があった場合はテストコードを作り直さなくてはならなくなります。リスト6のJuchuImplクラスはZaikoHikiateImplクラスに依存しています。そのためJuchuImpl単体のテストを行おうとしても、ZaikoHikiateImplクラスのhikiate()メソッドにバグがある場合テストができませんでした。
public class JuchuImpl implements Juchu { public void juchu(Integer syohinCode, Integer suryo) { ZaikoHikiate hikiate = new ZaikoHikiateImpl(); hikiate.hikiate(syohinCode, suryo); // 受注を処理 } }
SpringをはじめとするDIフレームワークでは、他クラスへの依存はインターフェイスを介したものになります。インターフェイスを介するため、呼び出す側はそのインターフェイスを実装しているクラスの完成を待つ必要がなくなります。DIコンテナを利用すればDIコンテナが設定ファイルの記述に従ってインターフェイスの実装クラスをセットします。そのため、設定ファイルの内容を切り替えることで単体テスト用のモックオブジェクト(ダミーオブジェクト)に切り替えることができます。例えば、例(リスト7)で挙げたJuchuクラスはZaikoHikiateImplクラスが完成していなくてもZaikoHikiateインターフェイスを実装したモックオブジェクトをDIコンテナにセットしてJuchuImplの単体テストを実行できます。
また、こうしておくことで依存クラスに修正が入ったとしても、初めに作成したモックオブジェクトを利用するテストコードが、そのまま利用可能です。
DIコンテナによって、「単体テストコードの再利用性」をも向上することができるのです。
public class JuchuImpl implements Juchu { private ZaikoHikiate hikiate; public void setHikiate(ZaikoHikiate hikiate) { this.hikiate = hikiate; } public void juchu(Integer syohinCode, Integer suryo) { hikiate.hikiate(syohinCode, suryo); // 受注を処理 } }
さらに、Springを利用したアプリケーションはEJBのように特別なインターフェイスを実装したクラスではなく、普通のJavaオブジェクト(POJO:Plain Old Java Object)で構成されます。そのため単体テストの実施にはアプリケーションサーバーなどの特別な環境を必要としないこともテストを行ううえでは大きな利点といえます。
このようにDIによって単体テストの自動化が行いやすくなります。「テストコードの複雑さ」や「テストコードの再利用性の乏しさ」に不満を持っていて、単体テストの自動化を行っていなかったプロジェクトは、ぜひともDIコンテナと単体テスト自動化ツールの併用をお勧めします。
Springはいままでのフレームワークと連携させることができます。そのため、今までのフレームワークの知識を無駄にせず、活用することができます。
例えば、開発現場で多く利用されているフレームワークであるStrutsとSpringを連携させることができます。そうすることでDI機能などSpringのメリットが利用でき、プロジェクトメンバーのStrutsに関する知識や経験も生かすことができます。加えて、アーキテクチャに柔軟性が備わるメリットもあります。次にその柔軟性について説明します。
筆者が参画したプロジェクトでは、SpringのほかにStruts、Hibernateを使用していました。これらのフレームワークとSpringの連携は設定ファイルの記述のみで可能であり、複雑なものではありません。この連携により以下のようなことが実現できました。
宣言的トランザクション管理とは、トランザクションに関するコードを書く代わりにSpringの設定ファイルにトランザクションの属性やその対象となるメソッドなどを記述することです。プログラム中にトランザクションの開始や終了、ロールバックなどを書く必要がなくなります。
上記のようにSpringと連携して、ほかのフレームワークを利用することで、フレームワークの変更を行うような場合でも対処が可能となります。「フレームワークを取り換える」というのはまれなケースだと思われるかもしれませんが、実際に筆者のプロジェクトでは次のような「一部の処理のみデータアクセスフレームワークをHibernateからJDBCに取り換える」という事例がありました。
データベースの2つのテーブル「受注明細」と「商品」を商品コードで結合したいのですが、データベースの設定の関係でHibernateではうまく結合できず、受注明細の件数だけクエリを発行しなければならなくなりました(表3)。
そこで、データベースにアクセスする実装クラスをHibernateからJDBCを利用するものに取り換えました。呼び出す側はインターフェイスに対してアクセスしているため、設定ファイルを書き換えてHibernateを利用するクラスからJDBCを利用するクラスに変更して、2つのデータアクセスフレームワークを共存させることで対処ができました(リスト8)。トランザクション管理も、HibernateとJDBCで同じ方式が利用できるため変更は要りませんでした。
Springを使ってフレームワークを連携させていたため、上記のような不測の事態にも比較的容易に対応することができました。アーキテクチャに柔軟性を持たせておくことは、リスク回避、リスク軽減になると感じました。
<bean id="juchuMeisaiDao" class="JuchuMeisaiHibernateDaoImpl"> <property name="sessionFactory" ref="sessionFactory"></property> </bean>
↓
<bean id="juchuMeisaiDao" class="JuchuMeisaiJdbcDaoImpl"> <<property name="dataSource" ref="dataSource"></property> </bean>
ここまでSpringの利点ばかり述べましたが、弱点だと感じる面もあります。その弱点とは、オブジェクトの依存関係を記述する「設定ファイル」にあります。
設定ファイルはコンパイルの対象にならないXMLファイルであるため、記述した内容の確認が行われるのが、動作するときにまで遅れます。つまり、設定ファイルに記述した内容の中の記述にエラーがあることの確認やクラスの存在確認が行われるのは、設定ファイルをSpringが読み込んだときです。これはプリコンパイルを行い、静的型付けを行うJavaの長所を消してしまうような弱点です。
例えばWebアプリケーションであれば、デプロイしたときにようやくチェックが動作します。設定ファイルを書いた時点でエラーが判明しないため、設定ファイルを利用しない場合よりも効率が悪い部分があります。
XMLの文法間違いやスペルミスといったものに関しては、ツールを利用してミスを早めに発見するのがよいでしょう。Springであれば、EclipseのプラグインであるSpringIDEがあります(http://springide.org/project)。これを使うことで、ケアレスミスは抑えることができます(画面1)。
ただし、こういったツールを活用しても「設定ファイルのサイズが大きくなってしまう」という問題点や、「“クラス名を変更する”などのリファクタリングによる影響範囲をコンパイル時に知りたい」という要望は解決できません。サイズが大きくなれば内容の把握に時間がかかるなど生産性向上のメリットが損なわれてしまいます。ファイルを分割することもできますが、根本的な解決とはなりません。Springは現在バージョン2.0を開発中ですが、XMLファイルサイズ増大への対応予定は告知されていないため(注)、おそらく残ったままとなるでしょう。一方、同じDIコンテナ+AOPのフレームワークであるSeasar2では、設定ファイルを不要とする方向へ進んでいます(http://www.seasar.org/)。DIコンテナのプロジェクトへの導入を検討する際にはこの辺りの点も比較するのがいいでしょう。
注:Springの対応予定告知は、「Browse Project − Spring Framework」で公開されています。
今回はSpringが提供するDI、AOPによって開発効率がどのように向上するのかを述べました(表4)。
事例 | 内容 | |
---|---|---|
仕様変更への対応 | AOPを利用し、既存のソースコードを修正することなく対応でき、後の管理も楽になった | |
単体テストの実施 | DIによりクラス間はインターフェイスを介した依存となるため、単体テストを実施しやすくなった | |
他フレームワークとの連携 | Springを通して他フレームワークを利用するため、HibernateからJDBCへ容易に変更できるなどアーキテクチャに柔軟性が備わった | |
設定ファイルの肥大 | ケアレスミスの減少にはツールが助けとなるが、ファイルのサイズは大きくなるため内容の把握に時間がかかる | |
今回で、本連載の記事は最後です。連載開始から随分と時間がたってしまいましたが、その間にSpringフレームワークやDIコンテナはJavaの定番フレームワークとして普及しつつあります。JavaEE5(J2EEの新バージョン)では、EJBのアーキテクチャがDIコンテナを参考にした形になることからも、今後もJavaテクノロジーで重要なポジションのテクノロジーになると考えられます。
本連載が、Springの導入を判断する際の参考になればと思います。最後までお付き合いいただき、ありがとうございました。
Copyright © ITmedia, Inc. All Rights Reserved.