ClockWrapperパターンを守れないときにdjUnitのVMOが役立つ

ClockWrapperはある種のパターン(と、例えば安易なnew Date()に対するアンチパターン?)の提示。これは一度「ユニットテストできねえ!」という話になって教訓のようになることも多いのだけど、同種の問題に以下のようなものもある

  • new File() しているケース (そして、そのファイルの状態に応じていろんな動作をする場合など)
  • new Date() もしくはその兄弟のクラスをいろいろいじっているケース

ほかにも testability の問題として

  • ツブのでかい、ユニットテストとしては切り離したい、あるクラスのstaticメソッドへの依存がある

などもある。

これらの問題は djUnit を使うことで解決される。具体的には djUnitのVMO(Virtual Mock Objects)というものを使うのだけど、こいつがメチャメチャ便利。
テスト時にシステムクラスローダーを差し替えることで java.util.Date のコンストラクタを乗っ取ったり、staticメソッドを乗っ取ったりできるのでかなりのケースで「テスト可能」の幅が広がる。

一応、これを継承してテストケースを作りなさい、というDJUnitTestCaseというクラスも用意されているが、作りが非常にシンプルなので(少なくともいまのところ)このクラスをあえて使う必要はない。S2TestCaseとか、好きなテストケースに混ぜて使えるわけだ。

まず、MockObjectManager(MOM)を初期化しておくこと。

	protected void setUp() throws Exception {
		super.setUp();
		MockObjectManager.initialize(); // これ
	}

で、あとは事前に振る舞いを上書きしておいてやる。EasyMockなどのrecord->verify、呼ばれなくてもエラー、という形式ではなくて、やや懐かしい話かもしれないがS2のMockInterceptor的な振る舞いをすると考えるとわかりやすい。

さて、例えば new Date() を上書きしてしまおう. 日付の境界条件をテストしたい、というケースを想定してみる。

public void testなんとか() throws Exception {
  SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
  MockObjectManager.addReturnValue(Date.class, "<init>", format.parse("2008/02/29 00:00:00"));

  testTarget.someMethod();
  assertEquals(....
}

この例では Dateのコンストラクタを上書きしてしまっている。の戻り値が構築後のインスタンスなので、format.parseを使って好きなDateオブジェクトを返してやる、というわけ。

同様の方法で、たとえばnew File()もテストリソースの中に準備したファイルオブジェクトへの参照を返すことが出来る。読み出し専用になっていたり、ディレクトリだったり、さまざまなケースをテスト可能にできる、というわけ。

TDDなんかを実践していると、設計、実装がtestabilityを意識したものに洗練されてくる、という価値があると思うのだけど、ClockWrapperはイヤだ、とか諸般の事情で「いまさら無理だよー」とかいうケースには有効。是非djUnitをオススメしたい。業者乙>おれ。

個人的には、java.util.Dateとかって、first-class objectに近い存在だと思っている(条件は満たしていないかもしれないが...)ので、ClockWrapperなんて話をするととめどなくなってしまうのではないか、と思っている。new File()も、new Date()も同じこと。FactoryともFacadeとも見分けがつかないラッパーだらけになってしまいそうならいっそのことdjUnitを選択するのもありかと思う。

ちなみに、eclipseプラグイン、antタスクとして利用できるほか先述のエントリの通りmaven2でも利用できることを確認した。システムクラスローダーさえ差し替えられるならどんな状況でも利用できるかもしれない。