モヒートはモッキングフレームワークで味はとても美味しい

Thu Sep 19, 2013

mojito (モヒート)

モヒートは、キューバ・ハバナ発祥のカクテルの一つ。
由来は、新大陸として注目されていたアメリカ諸国から得られる富をコントロールする名目で、英国女王エリザベス1世が、スペイン領の都市を略奪する海賊達の手助けをしていた16世紀後半、海賊フランシス・ドレイクの部下であるリチャード・ドレイクが、1586年にモヒートの前身となる飲み物「ドラケ(draque)」をキューバの人々へ伝えた、という説が有力。
ラムをベースにソーダ、ライム、砂糖、ミントを加えたもの。ミントとソーダの清涼感が暑い夏にぴったりな「夏と言えば」の定番カクテル。

アーネスト・ヘミングウェイが好んで飲んでいた話は有名である。

mockito (モヒート)

モヒートは、Javaのモックライブラリ。
モックライブラリは他にもいろいろあるけど EasyMockと比べても mockitoの方が簡潔に書ける。
導入するとモヒートを飲んだあとのようにスカッとする。

“Mockito is a mocking framework that tastes really good!” とのこと。(公式)

mockitoナシ

今までのやり方。まずモッククラスを定義して、

import com.android.volley.Network;
import com.android.volley.NetworkResponse;
import com.android.volley.Request;
import com.android.volley.VolleyError;

public class MockNetwork implements Network {
    private byte[] mFakeResponseData = null;

    public void setFakeResponseData(byte[] data) {
        mFakeResponseData = data;
    }

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        return new NetworkResponse(mFakeResponseData);
    }
}

テストするときに返したいデータをセットする。

MockNetwork mockNetwork = new MockNetwork();
mockNetwork.setFakeResponseData("{\"code\":200}".getBytes());

mockitoアリ

このメソッドが呼ばれたときにこれを返す、とするだけ。

Network mockNetwork = mock(Network.class);
when(mockNetwork.performRequest(any(Request.class))).
        thenReturn(new NetworkResponse("{\"code\":200}".getBytes()));

インタフェースが統一されることによって、次にテストを書く人が「MockNetworkというクラスがあるらしい、ふむふむ、setFakeResponseDataに渡したbyte列がperformRequestで返ってくるのか」と調べる時間を省くことができる。

特定のオブジェクトの一部のメソッドの振る舞いを変えるときもカンタン。

doReturn(new HashMap<String, String>() { { put("fake", "foo"); } }).when(spyRequest).getHeaders();

ちなみにdependencyを解決できなかったので、jarを落として ./src/instrumentTest/libs/ に配置して以下のようにした。

instrumentTestCompile fileTree(dir: './src/instrumentTest/libs', include: '*.jar')

モヒートにギョームでもプライベートでもお世話になってる。


↑ここまでモヒートの話↑
↓ここまで他のフレームワークの紹介↓

Fest Android

安心と信頼の Square 製テストフレームワーク。(Squareが公開してるライブラリは本当にどれもレベルが高い)
元ネタは Fixtures for Easy Software Testing のAndroid拡張となっている。

REGULAR JUNIT

assertEquals(View.VISIBLE, layout.getVisibility());
assertEquals(VERTICAL, layout.getOrientation());
assertEquals(4, layout.getChildCount());
assertEquals(SHOW_DIVIDERS_MIDDLE, layout.getShowDividers());

FEST ANDROID

assertThat(layout).isVisible()
    .isVertical()
    .hasChildCount(4)
    .hasShowDividers(SHOW_DIVIDERS_MIDDLE);

calculon

こちらも便利メソッドを提供している。

// direct assertion on current activity
assertThat().inPortraitMode();
assertThat().viewExists(R.id.launch_bar_button);

// assert specific condition on current activity
    assertThat().satisfies(new Predicate<Activity>() {
    public boolean check(Activity target) {
        return target.isTaskRoot();
    }
});

Fest Androidとの違いは、calculonはStoryTestを提供しており、画面遷移を伴うストーリーをテストとして実行することができる。

Robolectric + Spock

この動画で紹介されているGroovyの元祖PowerAssert系テストフレームワーク Spock を頑張ってAndroidで動かすというもの。

RobolectricはAndroidのテストをJVM上で実行するためのフレームワークで、AndroidのクラスをJavaのShadowクラスに変換して実行するしくみになっている。 JVMでテストが実行できるようになるということは、Groovyでもテストが書けるということなので、Robolectricを導入すればSpockも使えるようになる。

Robolectric

@Test
public void testDialogContent() {
    // given
    final MainActivity mainActivity = new MainActivity();
    mainActivity.onCreate(null);

    // when
    mainActivity.button.performClick();

    // then
    final ShadowAlertDialog dialog = (ShadowAlertDialog) Robolectric.shadowOf(ShadowDialog.getLatestDialog());
    Assert.assertEquals("title", dialog.getTitle());
    Assert.assertEquals("Ok", dialog.getButton(AlertDialog.BUTTON_POSITIVE).getText());
    Assert.assertEquals("Cancel", dialog.getButton(AlertDialog.BUTTON_NEGATIVE).getText());
    Assert.assertEquals("Dismiss", dialog.getButton(AlertDialog.BUTTON_NEUTRAL).getText());
    Assert.assertEquals("Dialog Content", dialog.getMessage());
}

Robolectric + Spock

def "should displayed dialog's button has good text"() {
    given:
    def mainActivity = new MainActivity()
    mainActivity.onCreate(null)

    when:
    mainActivity.button.performClick()
    def dialog = (ShadowAlertDialog) Robolectric.shadowOf(ShadowDialog.getLatestDialog());

    then:
    dialog.getButton(number).text == value

    where:
    number                      | value
    AlertDialog.BUTTON_POSITIVE | "Ok"
    AlertDialog.BUTTON_NEGATIVE | "Cancel"
    AlertDialog.BUTTON_NEUTRAL  | "Dismiss"
}

導入コスト、学習コスト、効果を鑑みつつ、引き続きテスティングフレームワークをテイスティングしていきます。



  « Previous: Next: »