Singletonをモックする。

このエントリーは、Java Advent Calendar 2013 の参加エントリーです。テストが絡む話ですが、TDD Advent Calendar ではありません^^:

前日は、cero_tさんのラムダ禁止について本気出して考えてみた - 9つのパターンで見るStream API - Taste of Tech Topicsでした。

Javaに限らずどの言語でも、テスタビリティを考えた設計をした場合、Singletonをさけるというのは定石です。*1しかし、Javaを用いたシステム開発の場合、XMLとどうしても縁が切れないということがあり、XMLのパース等重い処理をクライアントからのリクエストごとに行うということは避けたいという事情があります。よって、Javaのシステム開発とSingletonは切っても切れない縁だと言えるでしょう。

しかし話を戻しますが、ユニットテストの網羅性を確保するためには、Signletonのクラスの挙動にバリエーションを持たせる必要があります。

例えば、XMLやデータベースによる設定値を保持するクラスがあったとして、その設定値により次のように処理が別れる場合、 カバレッジを確保するためにはSingletonのクラスの返り値にバリエーションを持たせる必要があります。一つしか状態を持たないSingletonをどうモックするのか...? 以下、解法です。mockito前提です。

まず、Singletonのクラスは普通に書きます。checkstyle等でfinalにしろと言われますが、ぐっとこらえてください。コンストラクタをprivateにすることをお忘れ無く。

次に、その Singletonのクラスを呼び出す側ですが、普通は

TaxSingleton.getInstance().getRate();

という風に書くと思うのですが、ここでは

TaxSingleton singleton = TaxSingleton.getInstance();

というように、呼び出す側のクラスのフィールドにSingletonのクラスのインスタンスを持たせます。

Singletonですから、フィールドにインスタンスを持たせたとしても、インスタンスの総数は1個ですよね。

そして、テストクラス側で、そのSingletonのクラスのモックを作って、テスト対象のフィールドに代入させるのです。mockitoがコンストラクタがprivateなクラスであっても、バイトコードエンジニアリングで継承してモックを作れることを使用したトリックです。

以上のようなやり方で、Singletonのクラスに状態を書き換えるAPIを用意するなどの抜け穴を用意すること無く、Sigletonのクラスをモックできるようになりました。しかし、Singletonを使うということは、どうしてもシステムのアーキテクチャーの中で密結合になるポイントを作ることになりますので、アーキテクチャーの設計時に重要な決断を迫ることになりますので、慎重に検討してください。

*1:例えば, Singleton を避ける http://blog.yuyat.jp/archives/1500