2014年5月8日木曜日

【追加】最強モックツール JMockit その13 DI(Spring)対応

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

只今、私の現場ではこのブログを参考資料としてJUnit開発を進行中です。

その現場にて必要になった新情報を補強連載しております。

DI

今回の記事で重要になるキーワード、それは「DI」です。
依存性の注入(Dependency injection)という意味でして、Javaのクラス宣言を普通に「new ○○」とやるのではなく、外部XML等に宣言して
クラス間の結合を疎にするというソフトウェアパターンです。

このDIの機能を提供してくれるフレームワークをDIコンテナと言います。

DIコンテナの代表格と言えば、やっぱり「Spring」ではないでしょうか?

老舗にして大御所。
Javaを使っている人は、いつかどこかでお世話になるフレームワークかと思います。

DIの利便性

DIを使うことの利点、それは「クラス間が疎結合になる」ことです。

と言いましても、ピンと来ない人も多いかもしれませんね。


「クラス間が疎結合になったから何だって言うんだ?」


全く普通の感想かと思います。

DIコンテナを使うと、クラスを生成する際に普通に「new ○○」とは書けなくなって、
XMLファイルに色々宣言したりと手間が掛かるようになります。

私もいくつかの現場でSpringを使った事がありますが、振り返って考えてみると、
「クラス間が疎結合になり、ただ面倒になっただけ」という失敗ソースが多かったですね。

Springは結構難しいフレームワークなので、深く理解してからクラス設計しないと大失敗に陥ってしまいます。

DIコンテナについて記述していくと一回の記事で終わりませんので手短に行かせて貰いますが、
DIコンテナの利便性の一つ、それは「スタブが使い易い」ということです。

DIによるスタプ

まずはDIを使った簡単なサンプルを以下に記述します。

public class Sample01 {
 
 /** DIコンテナで自動セットするDAO */
 @Autowired
 private SampleDao sampleDao;
 
 public void test() {
  
  //DB検索実行
  sampleDao.findAll();
  
 }

}

このソース、「new SampleDao()」なんてやってる箇所が無いですよね?
SampleDaoはDIによってクラス生成時に自動セットされますので、自分でnewする必要は無いのです。
「@Autowired」のアサーションが、自動セットするフィールドであることを示すマークです。

これは分かっている人にとってはソースがスッキリして良いのですが、分からない人にとっては混乱するだけという、ちょっとハードルのある機能です。

さて、この「SampleDao」は、実はインターフェースでして、実クラスではありません。
実クラスが何なのかはソース中ではなく外部クラスに定義されています。


DIの特徴:ソース中に出てくるのはインターフェースだけ。実クラスは出てこない。


では、実クラスはどうやってセットされるのか?
それを外部のXMLファイルに記述するわけです。

<bean id="target" class="jp.co.net.genesis.sample.Sample01Impl" / >

こんな感じです。

インターフェースの中身は外部定義ファイルに定義されます。
これがDIコンテナの最大の特徴。


  • 本番時は本番用の外部定義ファイルを読み込む。⇒本物のクラスを定義
  • テスト時はテスト用の外部定義ファイルを読み込む。⇒テスト用スタブを定義


こうすることで、ソースの中身に手を加えることなく本番モードとテストモードを切り替えることが出来るわけです。

この機能は大規模開発で役に立つことが多いですね。
複数のチームで一つのシステムを作るような大規模開発の場合、一つのチームの進捗遅れや手違いで全チームが影響を受けることもしばしば。

しかし、DIコンテナを使うことで、
「本番用ソースは別チームが開発中」、「こっちは先にスタブでテストを済ませておく」みたいな戦術が使えるようになり、
チーム毎やクラス毎の独立性が確保されるようになるわけです。

DIのモック化

しかしですね、上記の場合、「わざわざ本番用とスタブの2コのクラスを作らなければならない」という手間もあるわけですよ。


スタブを作るのが面倒臭い!!


結構あるんですよ。
ちょっとの機能なのに一々スタブを作るなんて面倒でやってられない、とか。

だったら最初からDIコンテナなんて使うな、と言いたい所なのですが、現場の事情によることもありますので、
JMockitにて柔軟に対応していきましょう。

public class Sample01Test extends ContextManager{

 private Sample01 mockSample01;

 /** DIのモック  */
 @Mocked
 private SampleDao sampleDao;

 @Test
 public void testDoTest() {

                mockSample01 = getBean(Sample01.class)

  new NonStrictExpectations() {{
   Deencapsulation.setField(mockSample01, sampleDao);
        }};

        mockSample01.test();

 }

}

さて、JMockitは内部で「new」しているクラスも勝手にモック化する超強力ツールであることは第4回でご紹介しましたが、
DIコンテナの場合は「@Autowired」のアサーションで自動セットしており、内部でnewしているわけでは無い為か、普通にやってもモック化されません。

厄介ですね。

こういう場合は、荒技を使います。


  1. とりあえず普通にコンテナ経由でクラスを生成して、@Autowiredにより自動セットして貰う。
  2. 自動セットされた後でモックに上書き!!

privateフィールドを上書きするという超荒技です。


「Deencapsulation.setField(mockSample01, sampleDao);」というソースが、その上書きを行っている箇所です。


簡単に使えますね。
Javaクラス設計の根底をひっくり返す裏技ですが、そこはJMockitの黒魔術ということでご了承下さい。

終わりに

引き続き、現場からのフィードバック記事を連載します。

リクエストがあればこのような形で順次お答えしていきたいと思います。

0 件のコメント:

コメントを投稿