スマートデバイスアプリ開発のあれやこれや

Mockito事始め

 Javaのモックライブラリ Mockito(もきーと?)を使ってみたので
導入方法や簡単な使い方を整理してみます。

f:id:cross-xross:20150919155517p:plain

1. モックライブラリって何?

 モック=模造品、まがい物 という意味です。


 ある程度規模が大きいソフトウェアを開発するケースだと
自分担当のクラスは完成済みでテストしたいんだけど、クラスの中で
利用している別クラスが未完成
...なんてことはよくあると思います。
(未完成でもIFは設計済みなので完成できてしまう……!)


 よくある解決策として、未完成のソースコードをテスト用に書き換えて
とりあえずテストしてしまう……なんてやりがちですが、よくよく考えると
テストのためにテスト対象を書き換えることになり本末転倒です。


 そんなケースで、テストコードの中で未完成クラスを模造品(モック)に
置き換えてやる
ことで、テスト対象を汚すことなく完成済みクラスを
テストすることが出来ます。

2. Mockitoをインストールする

 公式サイトからMockitoのライブラリを入手しましょう。
Mavenをお使いの方であれば、pom.xmlに以下の記述を追加するだけでOKです。

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.10.19</version>
</dependency>

3. モックオブジェクトを使いたい状況の仮定

 では、早速Mockitoを使っていきます。
今回は完成済みクラスとして以下のような「SampleLogic」クラスが存在し

public class SampleLogic {
	
	private final String ODD  = "偶数";
	private final String EVEN = "奇数";
	// 未完成クラス
	private TargetClass target;
	
	/**
	 * Constructor
	 */
	public SampleLogic() {
		if (target == null) {
			target = new TargetClass();
		}
	}
	
	public String checkNumberType() {
		// 偶数と奇数を判断して文字列を返却する
		int randamNumber = target.generateRandamNumber();
		if (randamNumber % 2 == 0) {
			return ODD;
		} else {
			return EVEN;
		}
	}
}


 その内部で利用している「TargetClass」クラスが未完成という仮定で説明します。

public class TargetClass {
	
	public int generateRandamNumber() {
		//TODO: 乱数を返却する
		return 1;
	}
}


 TargetClassのgenerateRandamNumberメソッドは未完成で
ランダムな値を返すはずが固定値しか返却しないため、このままでは
命令網羅を実現できません。そのため、ランダムな数値を返却する
TargetClassのモック(模造品)を作る必要があります。

4. モック作成の準備

 テストクラス作成の準備をしていきます。
完成品を見た方が早いので晒してしまいます。

public class SampleLogicTest {
    @Mock
    private TargetClass mockObject;
    @InjectMocks
    private SampleLogic testTarget = new SampleLogic();
	
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }
}


 今回のケースでは、未完成のTargetClassのモックを作成したいので
メンバ変数でTargetClassを定義した上で@Mockのアノテーションを付けます。
@Mock付きのメンバ変数がモック対象となる点を覚えておいてください。

    @Mock
    private TargetClass mockObject;

 

 一方、モックを利用する側のクラスについてもアノテーションが必要です。
SampleLogicが利用するので@InjectMockというアノテーションを設定します。
こちらはモックの挿入(インジェクト)先を指定する意味を持ちます。

    @InjectMocks
    private SampleLogic testTarget = new SampleLogic();


 そして、忘れちゃいけない設定があります。
今回はアノテーションベースでMockitoを扱うため@Before付きの
前処理メソッドで初期化処理を実装しておく必要があります。

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

5. モック作成

 実際にモックを作成していきます。
基本的には@Mock付きのmockObjectを弄るだけです。
(インジェクト設定は既に済んでますので)

@Test
public void test() {
	// generateRandamNumberメソッドのモックを作成する
	when(mockObject.generateRandamNumber()).thenReturn(2);
	String result = testTarget.checkNumberType();
	assertEquals("偶数", result);
}


 whenメソッドでモックしたいメソッド(generateRandamNumber)を指定します。
そしてそのまま、thenReturnメソッドの引数で戻り値で返したい値を設定します。
基本的にwhen(どのメソッドをモックする?)/then(どのようにモックする?)という感じで直感的です。


 結果、checkNumberTypeメソッドの内部で実行されるTargetClassは
模造品(モック)で置き換えられているので、generateRandamNumberメソッドも2を返します。
それによって、checkNumberTypeメソッドも偶数を返すようになりテストは成功する......。

6. モックしずらいテストクラス

 とはいえ、Mockitoも万能ではありません。
例えば今まで説明に使っていたテスト対象クラスがこう変わったらどうでしょうか?

	public String checkNumberType() {
		TargetClass target  = new TargetClass();
		int randamNumber = target.generateRandamNumber();
		if (randamNumber % 2 == 0) {
			return ODD;
		} else {
			return EVEN;
		}
	}


 @InjectMockアノテーションモックを差し込めるのは
メンバ変数だけなので、ローカル変数になってしまうとお手上げです。

そのため、設計段階からMockitoでテストすることを意識することが重要です。


 とはいえ、メンバ変数に切り出せない(例:スレッドセーフにならないクラス)場合も
ありえるのでそういう場合は泣く泣く諦めるしかありません......。
Mockitoは便利だけれど、万能ではない、という点を覚えておくと良いと思います。