テストケースくらいはなるべく特定のフレームワークに依存させたくないという気持ちはあるものの、さすがに Hibernateやらといったヘヴィな奴をインスタンス化する長い道のりを手でコーディングしたり、単一責任の原則に基づいて真面目に分割されたコンポーネント群のワイヤリングを手でコーディングしたりするのはかなり厳しいものがあるので、そこは目をつぶってテストケースもやはり Springの助けを借りて実装するのが現実的だ。
なお JUnitと Springを連携させるには、spring.jarの他に spring-test.jar が必要となる。
Eclipseについてる JUnitを捨てる
Eclipseについている JUnitはちょっと古くて、Springに対応するために必要なインターフェイスJUnit4ClassRunnerが存在していない。なので Eclipseに標準で付属している JUnitをビルドパスから外し、最新の JUnitへビルドパスを通す必要がある。その場合でも "Run as"→"JUnit TestCase"はちゃんと動くので心配ない。
テストケースに Beanをインジェクトさせる
テストケースに Springサポートを適用する一番シンプルな方法は、下記のふたつのアノテーションをつけてやることである。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
これらのアノテーションがついたテストケースを走らせると、最初にテストケースと同じパッケージ階層とクラス名+"-context.xml"という名前を持つ Bean定義ファイルが読み込まれる。
テストケースに @Autowired (もしJava EEのアノテーションが使えるなら @Resource でも良い)の付いたプロパティがあれば、それぞれのプロパティと同じ型の Beanが自動的にインジェクトされる。
<!-- src/test/com/acme/DataImporterTest-context.xml -->
<beans>
<!-- 中略 -->
<bean id="dataImporter" class="com.acme.DataImporterImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
package com.acme;
// 中略
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DataImporterTest {
@Autowired
DataImporter dataImporter;
public void testDoImport()
{
dataImporter.doImport();
}
}
これなら、テストケースの中に何らインスタンス化やワイヤリングの処理を記述しなくても良く非常に簡潔である。
読み込むBean定義ファイルを変更したり名称を指定してプロパティに Beanをインジェクトしたり(@Qualifierアノテーションを使う)も出来るので、詳しくは Spring 2.5のドキュメント
8.3.7.2. Context management and caching あたりを参照されたし。
トランザクション制御も自動化する
典型的には、データベースアクセスを行うコンポーネントに適用するテストケースの各メソッドはそれぞれがひとつのトランザクション単位として考えられるだろう。したがって、各テストメソッドの開始時点で自動的にトランザクションがBEGINされ、終了時には自動で COMMIT又は ROLLBACKが行われると便利である。
そうしたい場合は、先の @RunWith, @ContextConfigurationアノテーションに加えて下記のアノテーションをつけてやると良い。
@TransactionConfiguration
@Transactional
各テストメソッドの実行にあたり、Bean定義ファイル内で定義された transactionManagerという名前のトランザクションマネージャを勝手に使ってトランザクションの制御を行ってくれる。
(使用するトランザクションマネージャの名前は@TransactionConfigurationアノテーションのオプションで変更が可能)
デフォルトでは、各テストメソッドの終了時にはトランザクションがロールバックされるようになっている。つまりテストの実行によってデータベースの内容を変更しないということだ。ユニットテストは外部リソースの状態から独立しているべきなのでこの動作が理想的なのではあるが、どうしてもテストケースを回した後に Microsoft Accessやら Navicatやらといった GUIツールでテーブルの中身を確認してみたい場合もある。そういう時は @TransactionConfigurationアノテーションのオプションで defaultRollback=falseを与えてやるか、テストメソッドへ明示的に @Rollback(false) のようにアノテーションでロールバックを行わないことを記してやる必要がある。
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration
@Transactional
public class DataImporterTest {
@Autowired
DataImporter dataImporter;
@Rollback(false)
public void testDoImport()
{
dataImporter.doImport();
}
}
気になったのだが、テストメソッドがロールバックを行わない設定になっていると、テストが失敗した(キャッチされない例外が上がった、アサーションに引っかかった)場合でもトランザクションは容赦なくコミットされてしまうようだ。中途半端なデータをデータベースに残さないよう、実装の初期段階ではロールバックを行う設定でユニットテストを行う方が良さそうである。
Beforeと After
Springによるトランザクション管理が適用されたテストケースでは、JUnitの標準的な @Before, @Afterメソッドの他に @BeforeTransaction, @AfterTransactionメソッドを持つことが出来る。
@Before, @Afterはトランザクション内で実行されるが、@BeforeTransactionはトランザクションの開始前、@AfterTransactionはトランザクションの終了後に実行される。
少しだけ記述を減らす+α
もしテストケースが他のクラスを継承していないのであれば、@RunWith, @TransactionConfiguration, @Transactionalアノテーションを付ける代わりに AbstractTransactionalJUnit4SpringContextTests を継承させることもできる。
アノテーションの記述を省略できることに加え、このクラスにはデータベース操作のユニットテストを行う際に便利かもしれない下記のものが装備されている。
・Beanファクトリへのアクセス (applicationContext)
・ログAPIへのアクセス (logger)
・SimpleJdbcTemplateのインスタンス (simpleJdbcTemplate)
・データベース操作のテストに便利かもしれないいくつかのメソッド(countRowsInTable,deleteFromTables,executeSqlScript,setSqlScriptEncoding)
最後のはフィクスチャのロードなどに使うことを想定した機能かもしれないが、そのあたりを本格的にやるなら DbUnitを組み合わせて使うのが良いと思う。
言うまでもなく Javaは多重継承ができないので、DbUnitの DatabaseTestCaseを継承するかこのAbstractTransactionalJUnit4SpringContextTestsを継承するかの選択を迫られることになるのだが。
ラベル: Spring