2014年11月24日月曜日

RobolectricでActivityのテストをやってみる

このエントリーをはてなブックマークに追加 はてなブックマーク - RobolectricでActivityのテストをやってみる

RobolectricでどのようなActivityのテストが出来るか確認してみたのでメモ。

Github

作ってみたプロジェクトはGithubに置きました。

toshihirock/RobolectricSample

Androidアプリ側の実装について本記事には書いていないので実際のコードをご確認いただければ幸いです。

出来ること

  • Activityのライフサイクル(onCreate,onResumeなど)が呼ばれた際の挙動
  • ある操作においてどのようなIntentが投げられているかの確認(他のActivityを呼ぶ側)
  • 特定のIntentが投げられた場合のAcitvityの挙動の確認(他のActivityから呼ばれる側)

出来ないこと

  • 本当に画面遷移をしているかの確認

単体テストなので、当たり前ですが、画面遷移が本当にできているかの確認は出来ません。しかし、「特定の操作でIntentが正しく設定されること(Aボタンが押下された時にhogeというIntentを設定すること)」、「特定のIntentが投げされた時にhogeとなること」はそれぞれ確認できるので、画面遷移に近い確認はできると思います。

Activityのライフサイクル

テストでActivityのライフサイクルを変更できるので、ライフサイクルが変わった時に発生するはずの処理が正しく動作しているか確認できます。

Activity activity = Robolectric.buildActivity(HogeActivity.class).create().get();

以上の記述でonCreateが呼ばれます。 また、以下のようにすることでonResumeを発生させることもできます。

Activity activity = Robolectric.buildActivity(SubActivity.class).create().start().resume().pause().get();

例えばonPauseになった時にあるTextViewの内容が「onPause」になるコードがあった場合には以下のようにテストを書くことができます。

@Test
public void onPauseメソッドが呼ばれたタイミングでTextViewが変更できていること() throws Exception {
    Activity activity = Robolectric.buildActivity(SubActivity.class).create().start().resume().pause().get();

    TextView text = (TextView) activity.findViewById(R.id.textview);
    String actual = text.getText().toString();
    assertThat(actual, is("onPause"));
}

ある操作においてどのようなIntentが投げられているかの確認(他のActivityを呼ぶ側)

あるボタンを押下した時にIntentにputExtraが正しく設定されているかは以下のように確認できます。

@Test
public void ボタン押下時にmessageというIntentでFromDeckardActivityと設定されている事() throws Exception {
    Activity activity = Robolectric.buildActivity(DeckardActivity.class).create().get();

    // click button
    activity.findViewById(R.id.button).performClick();
    ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
    Intent intent = shadowActivity.peekNextStartedActivity();

    String actual = intent.getStringExtra("message");
    assertThat(actual, is("FromDeckardActivity"));
}

peekNextStartedActivityメソッドで起動した(はず)のIntentを取得できるので、そのIntentを確認することで設定が合っているか確認できます。また、peekNextStartedServiceメソッド及びpeekNextStartedActivityForResultメソッドを利用することでサービスの起動やStartActivityForResultを使って起動する際の挙動確認もできます。

なお、Intentに設定したクラス名の確認は以下のようにすることで可能です。

@Test
public void ボタン押下時にIntentにSubActivityクラスが設定されている事() throws Exception {
    Activity activity = Robolectric.buildActivity(DeckardActivity.class).create().get();

    // click button
    activity.findViewById(R.id.button).performClick();
    ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
    Intent intent = shadowActivity.peekNextStartedActivity();

    ShadowIntent shadowIntent = Robolectric.shadowOf(intent);
    String actualClassName = shadowIntent.getComponent().getClassName();
    assertThat(actualClassName, is(SubActivity.class.getName()));
}

特定のIntentが投げられた場合のAcitvityの挙動の確認(他のActivityから呼ばれる側)

呼ばれる側のActivityも挙動を確認できます。

@Test
public void messageというIntentが設定された場合にTextviewに内容を表示すること() throws Exception {
    Intent intent = new Intent();
    intent.putExtra("message", "FromDeckardActivity");

    Activity activity = Robolectric.buildActivity(SubActivity.class).withIntent(intent).create().get();

    TextView text = (TextView) activity.findViewById(R.id.textview);
    String actual = text.getText().toString();
    assertThat(actual, is("FromDeckardActivity"));
}

@Test
public void messageというIntentが設定されていない場合にTextViewに設定されていない旨が表示されること() throws Exception {
    Activity activity = Robolectric.buildActivity(SubActivity.class).create().visible().get();

    TextView text = (TextView) activity.findViewById(R.id.textview);
    String actual = text.getText().toString();
    assertThat(actual, is("not found message extra"));
}

設定されるはずのIntentを作成し、withIntentメソッドを利用することで挙動の確認ができます。

参考

画面遷移をrobolectricでテストする

Driving the Activity Lifecycle

2014年11月16日日曜日

Deckard (for Gradle)を使ってRobolectricとEspressoが使えるAndroidプロジェクト環境をサクッと作る

このエントリーをはてなブックマークに追加 はてなブックマーク - Deckard (for Gradle)を使ってRobolectricとEspressoが使えるAndroidプロジェクト環境をサクッと作る

Androidの単体テスト用フレームワークRobolectricとUIテストフレームワークEspressoが使える環境をDeckard (for Gradle)というテンプレートプロジェクトを利用して簡単に作成してみたのでその時のメモ。(といってもほとんどREADMEの内容そのままだが。)

上記テンプレートプロジェクトでは最初からRobolectrci、Espressoの実行環境(及びそれぞれの試験が一つずつ)あるので、まずは、これを使って環境を作り、その後は自分の好きなように編集していくと環境構築で手間取ることはないかと思います。

実行環境前提

  • AndroidSDKをインストール済み。また、AndroidSDKのAPI19をインストール済み(READMEではAPI18と書いてありますが、 compileSdkVersionが19なので19が必要です)
  • エミュレーターでAPI19のものが存在し、起動済み。また、adb devicesコマンドで認識できていること(Espressoを動かす場合のみ)

Espressoを動かさない場合にはエミュレーターの起動は必要ありません。 余談ですが、AndoidSDK同梱のエミュレーターは遅いのでGenymotionを使って、エミュレーターの起動を高速化すると作業が捗ります。

CUI環境で動かす

Deckard (for Gradle)

README.mdの通りですが、やっていきます。

1.ANDOID_HOMEの設定

GradleのAndroidプラグインにAndoidSDKのパスを教える必要があるため、環境変数ANDROID_HOMEの設定を.bashrc.zshrcなどお使いのシェル環境に設定します。

$echo "export ANDROID_HOME=/usr/local/android-sdk-macosx" >> ~/.zshrc
$source ~/.zshrc

パスの部分は各PCで配置しているAndoroidSDKのパスを設定してください。

設定が正しいか確認します。

$echo $ANDROID_HOME
/usr/local/android-sdk-macosx

2.テンプレートのダウンロード、プロジェクト名の変更

プロジェクトテンプートをダウンロードして解凍します。

$wget https://github.com/robolectric/deckard-gradle/archive/master.zip
$unzip master.zip

プロジェクト名は任意の名前に変更します。

$mv deckard-master my-new-project

3.Robolectricの実行

この状態ですでにRobolectricの実行は可能です。以下で実行が出来きます。

$cd my-new-project
$.gradlew clean test

ビルドが成功するとテストレポートも出力されています。

$open build/test-report/index.html

4.Espressoの実行

私の環境でEspressoを実行した際にはcom.android.dex.DexException: Cannot merge new index 65576 into a non-jumbo instruction!とか表示されてエラーになってしまいました。

対応方法ですが、以下のサイトに記載してありました。

Android Studioでビルド時にCannot merge new index xxxx into a non-jumbo instruction!

おそらく、Espressoはかなり多くのライブラリに依存しており、そのためにjumboModeにしないとエラーになるのではないかと思います。

jumboModeを有効にするためにbuild.gradleに以下の記述を追記します。

android {
    dexOptions {
       jumboMode true
    }
}

追記後、AndoridSDKのAPIが19のエミュレーターを起動し、adbコマンドも認識できることを確認します。 (AndoridSDKのAPIがbuild.gradleのものと同様でないとEspressoのテストが実行されないようです)

準備ができたら、以下のコマンドで実行します。

$./gradlew clean connectedAndroidTest

RobolectrciはJVMで動くので高速ですが、Espressoはエミュレータや実機が必要なため、少し時間がかかります。

こちらもビルドが成功すればテストレポートが出力されています。

$open build/outputs/reports/androidTests/connected/index.html

AndroidStudioで動かす

AndroidStudioではこのプロジェクトをそのままimportできるので、ここまでCUIで確認してからAndoridStudioに取り込み、自分の作成するプロジェクトに応じてAndoridManifest.xmlやソースコードを変えていき、自分のリポジトリにpushしていけば良いと思います。

テストの実行などはAndroidStudioのTerminalからでも実行できますし、Run/Debug ConfigurationでGradleのタスクを追加することでも可能です。