CDIをはじめよう
少し時間が経ってしまいましたが、2013年11月のJJUGナイトセミナーで Java EE 6 から導入されたDI/AOP仕様であるCDIについて話す機会を頂きました。
補足 : インタフェースを作るべきか
資料中では『インタフェースを作りましょう』と書いていますが、DIをユニットテストのしやすさを目的に使う場合は実装クラスをそのままインジェクションしても良いと思います。mockitoなどの使いやすいモックフレームワークが出てきているためです。mockitoを使うと、インタフェースをテスト用に実装した"ベタな"モックを作らなくても実装クラスの挙動を変更することができます。
mockitoを使う
例えば、以下のようなシンプルな課金処理サービスがあったとします。
CDIで顧客情報を持ってくるリポジトリと、クレジットカードでの支払い処理を担うクラスをインジェクションします。いずれのクラスもインタフェースは実装していないことにします。
public class AccountService { @Inject private CustomerRepository customerRepo; @Inject private CreditCardProcessor credit; public ChargeResult charge(String accountId, int amount) { // DBから顧客情報を取得 Customer target = customerRepo.find(accountId); if(target.isMembership()) { // 会員の場合は100円割り引き amount -= 100; } // カード支払い処理 ChargeResult result = credit.pay(target.getRegisteredCard(), amount); return result; } }
顧客情報を取得する部分はDBにアクセスし、カード支払い処理はカード会社のAPIにアクセスすることになるでしょう。 メソッドの真ん中に会員だけ100円引きという、いかにもビジネスロジックっぽい部分がありますが、ユニットテストしにくいです。
ユニットテスト (モック化部分のみ抜粋)
依存先クラスをmockitoを使ってテスト時にモック化しようと思います。
public class AccountServiceTest { @Test public void 会員の場合は100円引きされること() { AccountService sut = new AccountService(); // テスト顧客。会員にする。 Customer customer = new Customer("dummyId"); customer.setMembership(true); customer.setRegisteredCard(new Card("dummyCard")); // モックの生成 (mockito) CustomerRepository repoMock = mock(CustomerRepository.class); when(repoMock.find(anyString())).thenReturn(customer); // モックの注入 (jmockit) Deencapsulation.setField(sut, repoMock); // TODO 同じようにカード支払い処理もモック化する … }
mockメソッドでモックを生成し、when().thenReturn()で振る舞いを定義します。mock()メソッドで生成されたインスタンスは、すべてのメソッドがnull、またはプリミティブ型の場合はデフォルト値(intなら0)を返すようになっています。インタフェースも具象クラスでもmock対象とすることが可能です。返されたモックに対して、テストで必要な部分だけwhen().thenReturn()を使って振る舞いを定義します。上記例の場合は、CustomerRepository#find(String accountId)に対してどのような文字列が渡されたとしても、ダミーの顧客インスタンスを返すように定義しています。
Deencapsulation.setField(…)の部分は、jmockitというモックフレームワークに含まれるユーティリティクラスで、privateフィールドに生成したモックを注入するためにだけに使っています。jmockitは、staticメソッドのモック化など、mockitoにない機能を持つ強力なライブラリですが、覚えやすさという観点ではmockitoの方が有利かと個人的に思っています。
インタフェースの実装をモックフレームワークを使わずに作ると、結構なコード量になります。過去にjavax.sql.ConnectionやPreparedStatementなどのJDBCインタフェースのモックをベタで作ったことがある方はその分量がなんとなく想像できると思います。このように、具象クラスを直接CDIで注入しても、mockitoを使うと比較的少ないコードでモックを表現することができます。
mockitoについては公式のドキュメントまたは、技術評論社から出ている本 JUnit実践入門 に色々と解説があります。
インタフェース作る?作らない?どうする?
あくまで私見ですが、必要であればインタフェースを作った方が良いと思います。逆にいうと、DIのためだけにインタフェースを作らなくても良いと思います。
実装クラスを直接インジェクションした方が良い場合
- インタフェースを作っても実装クラスが一つだけで、この先も作る予定はない場合
- インタフェースを作る人と、実装作る人が同じ (ほとんどのケースで同じかと思いますが)
インタフェースを作った方が良い場合
- 資料中のスライド45〜で出てくる支払い処理(Paypal or Square)のように、共通点を持つ複数の実装がある場合
- 仕様(インタフェース)と実装(実装クラス)を分離したい場合
- DI使ってなくても、ここはインタフェース作成した方が良いなぁ と思った場合
最後に
大勢の前で話す機会はなかなかないので、私の拙い話を聞いてくださったみなさま、および日本Javaユーザグループに本当に感謝しています。ありがとうございました。