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のタスクを追加することでも可能です。

2014年10月24日金曜日

Gitlabで大文字小文字のリポジトリを正しく取り込む方法

このエントリーをはてなブックマークに追加 はてなブックマーク - Gitlabで大文字小文字のリポジトリを正しく取り込む方法

既存リポジトリを取り込むとき、リポジトリ名に大文字小文字がある場合にGitlabは勝手にに大文字も小文字にしてしまう。

これは困ったと思っていたら、実は直す方法が書いてあった。

Allow camelCase / PascalCase repo names

Settings→Rename repositoryで好きな名称に変更出来る。(ただし、危険な変更ではある旨が書いてあるので念のため、実行前にはリポジトリのバックアップなどしておくと良いと思います。)

2014年10月23日木曜日

[Git]過去のリビジョンに戻る場合でのgit checkoutとgit reset --hardの違い

このエントリーをはてなブックマークに追加 はてなブックマーク - [Git]過去のリビジョンに戻る場合でのgit checkoutとgit reset --hardの違い

仕事で過去のリビジョンに戻る方法はどうすれば良いのか?という質問があったのだが、git checkoutとgit reset –hardを使う場合の違いについてよく分かってなかったので調べてみた。

指定リビジョンに戻す

既に記載の通り、2つやり方がある。

$git checkout <commit>

もしくは

$git reset --hard <commit>

である。

ただし、二つは大きな違いがある。

git checkout <commit>

指定されたコミットIDのリビジョンに作業ディレクトリ内のファイルが変更される。ブランチは detached HEAD状態となり、この状態ではコミットなどを行ってもリポジトリに保存されない。(厳密には少し違うが) つまり、read only状態で指定リビジョンの状態確認が出来る。

元のブランチに戻る場合、以下のように元々のブランチ名を指定すれば良い。

$git checkout master

基本的にread onlyであるが、別途ブランチを作成する事で、リポジトリへの追記も可能。

git reset --hard <commit>

現在のブランチのまま、指定されたリビジョンに作業ディレクトリ、ステージング環境、HEADが変わる。もし作業ディレクトリに編集中のファイルがあったという場合にもその情報は失われてしまうので、実行の際には注意が必要。

また、元々のHEADの状態に戻る場合、git reflogを使うかORGI_HEADを使う必要がある。

$git reset --hard ORIG_HEAD

もしくは

$git reflog
$git reset --hard <commit>

結論

大半の場合、checkoutによる操作で事足りそう。そして、checkoutの方が安全。

参考

The git checkout Command

The git reset Command

detached HEAD状態から元に戻すコマンド (git, checkout, fix a detached HEAD, .git/HEAD, refs/heads/master)

git reset についてもまとめてみる

2014年10月14日火曜日

[Git]ローカルのコミットは入れず、リモートリポジトリの更新内容をブランチに取り込む

このエントリーをはてなブックマークに追加 はてなブックマーク - [Git]ローカルのコミットは入れず、リモートリポジトリの更新内容をブランチに取り込む

状態

  • masterブランチ。ローカルで1回コミットを行った。その後、リモートリポジトリが他のメンバーによって更新された。
  • devブランチ。ローカルで1回コミットをする前の状態のmasterからブランチを作成している。

やりたいこと

masterブランチのリモートリポジトリで行われた変更のみをdevブランチに反映したい。 ただし、masterブランチでのローカルで行われたコミット内容はdevブランチに反映したくない。

結論

$git fetch
$git checkout dev
$git merge origin/master

fetchでorigin/masterリモートブランチを最新化し、devブランチにorigin/masterをmergeする。

本作業と同時にmasterブランチにリモートリブランチ(origin/master)のコミットを反映しても良い場合には以下でもOK。

$git pull
$git checkout dev
$git merge origin/master

git pullはgit fetchgit mergeを実行するので、上記だとmasterブランチも変更される。 単純にやりたいことだけやるのであれば、git fetchの方が合っていると思われる。

追記(11/23) トピックブランチにマスターの変更を反映することを2回以上行うと大量のコンフリクトが発生する。

gitで双方向にmergeしているとひどいはまり方をすることがある件

上記より、トピックブランチへマスターの変更を反映するのはマスターへマージする直前のみとし、それ以外の場合にはマスターからブランチを再作成し、cherry-pickなどを利用するのが良いと思われる。

これは駄目

$git pull
$git checkout dev
$git merge master

これを実行するとローカルのコミットもdevブランチに反映されてしまうのでNG。

大切な事

  • git pullとはgit fetchとgit mergeが行われている事を意識する
  • git fetchが実行されると作業ディレクトリの内容はは変更されず、リモートブランチ(orgin/masterなど)のみが変更される
  • masterブランチとリモートブランチ(origin/master)ブランチは別のブランチでである

参考にさせて頂きました

2014年10月7日火曜日

EclipseのプロジェクトをGradleでビルドし、JenkinsでもRobolectricを動くようにする

このエントリーをはてなブックマークに追加 はてなブックマーク - EclipseのプロジェクトをGradleでビルドし、JenkinsでもRobolectricを動くようにする

最近のAndroid開発環境はAndroidStudioがデファクトスタンダードっぽいのですが、関わっているプロジェクトがNDKを利用しており、まだAndroidStudioでは未対応なので、Eclipseを使っております。

そんなプロジェクトなのですが、Robolectricを使ってテストしており、Jenkinsで実行させる為にコマンドラインで実行するまでにした事をメモしておきます。

どうやったか

Antでやる方法もあると思うのですが、Gradleだとそれ用のプラグインがあるのでGradleを使いました。

Github

とりあえず使える状態のサンプルをGithubにおいてあります。

RobolectricGradleSample

READMEに書いてあるような状態であれば以下で実行出来るかと思います。

$gradle clean test

EclipseでRobolectricを実行させる

以下の公式サイトに丁寧に記載があるのでこちらを参照。

Eclipse Quick Start

Gradleのビルドで必要なファイルを作る

Eclipseから簡単にGradleでビルドする時に必要なファイルを出力する事が出来るので、利用します。Eclipseを起動し、以下で完了です。

File→Export→Android→Generate Gradle build files

完了すると色々ファイルが出来ていると思うので、バージョン管理に登録しておきます。

サーバー環境でAndroidをGradleでビルドする

Androidをビルド出来る環境を作る

AndroidSDKをサーバーにインストールします。自分の場合、Linuxだったので対応するSDKをダウンロードして、後は自分のビルドしたいAndoridバージョンのSDKをインストールしておきます。

この時、 AndroidSDK Build-toolsAndroid Support Repositoryもインストールしておいてください。

AndoridSDK Build-toolsはGradleのビルド時にバージョンの指定が必要なので、インストールしたバージョンも確認しておいて下さい。

なお、CUIでAndoridSDKなどのインストール方法はこちらを参照。

特定のAndroid SDKをCUIでインストールする方法

GradleをGVMを使ってインストールする

サーバーにGradleをインストールしますが、普通にインストールするとGradleのバージョン切り替えが面倒なのでGVMを使います。

GVM

インストールします。

$curl -s get.gvmtool.net | bash

現在、利用可能なGradleのバージョンを確認します。

$gvm list gradle

1.12をインストールして利用します。(Androidをビルドするためのプラグインが1.1x系を要求するため)

$gvm install gradle 1.12
$gvm use gradle 1.12

環境変数ANDROID_HOMEを設定する

GradleのAndroidプラグインにて環境変数のANDROID_HOMEを指定する必要があるので、設定します。

$vi ~/.bashrc

追加します。

# Andorid_HOME
$export ANDROID_HOME=/Applications/adt-bundle-mac-x86_64-20140624/sdk/

適用させます。

$source ~/.bashrc

Jenkinsで実行する場合、Jenkinsの環境変数を設定する箇所で上記を設定してください。

build.gradleを編集する

Eclipseでプロジェクトを新規作成するとappcompat_v7とかもコンパイルしなければいけなかったり、lib配下にandroid-support-v4.jarとかが存在します。

Eclipseでビルドする時は良いのですが、サーバーでビルドする時に上記も用意したりするのは面倒なので、Gradleを使って依存を解決します。

という事で作成されたbuild.gradleを一部編集します。 自分の場合、以下のようにしました。

// Gradle自身の環境を設定
buildscript {

    // mavenCentralリポジトリを利用する
    repositories {
        mavenCentral()
    }

    // 利用するGradleプラグインは0.13を利用
    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.0'
    }
}

// すべてでmavenCentralのリポジトリを追加
allprojects {
    repositories {
        mavenCentral()
    }
}

apply plugin: 'android'

dependencies {

    compile fileTree(dir: 'libs', include: '*.jar')

    // 削除。下記のように記載することでGradleより取得
    //compile project(':appcompat_v7')

    // 追加。Gradleによってappcompatの依存関係を解決。
    // excludeの記述によってlib配下のsupport-v4との競合を避ける。
    compile ('com.android.support:appcompat-v7:18.0+') {
        exclude module: 'support-v4'
    }
}

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}

変更点は以下の通りです。

  • buildscriptを追加(2~13行目)
  • allprojectsを追加(16~20行目)
  • dependenciesの一部を変更(24~36行目)

buildscriptと記述されている場所はGradle自身の依存関係や利用するプラグインの情報を指定します。 今回の指定では以下2点を指定しています。

  1. buildscriptでの依存解決ではmavenCentralリポジトリを利用利用する
  2. com.android.tools.build:gradleの0.13を利用する

allprojectsと記述されている場所では以下を指定しています。

  1. 依存解決ではmavenCentralリポジトリを利用する(buildscript)

depnedenciesでは以下を指定、変更しました。

  1. appcompat_v7プロジェクトのビルドをする、という命令の箇所を削除
  2. コンパイル時に’com.android.support:appcompat-v7:18.0’以上が必要である事を記述
  3. appcompat-v7の依存を解決する時にsupport-v4に関する依存は解決しない

1点目のappcompatについてですが、Eclipseだとプロジェクトを追加した時にappcompat_v7も一緒に作成されて、ビルドが必要となります。ただし、サーバーでは面倒なので、この役目はGradleに任せる事としてコメントアウトしています。

2点目は1点目でコメントアウトしたappcompatについてGradleで依存解決する為の記述です。コンパイルする時にこのプロジェクトはappcompat-v7:18以上に依存しているよという事を記述しています。

3点目ではsupport-v4に関しての依存部分は解決しなくてよい、という事を指定しています。Gradleで依存解決を行う時は対象のものが依存しているものも一緒に依存解決しようとします。 試しにexcludeを消して、gradle dependenciesというコマンドをタイプして各ライブラリの依存関係を表示します。

$gradle dependencies 
・・・・
_debugCompile - ## Internal use, do not manually configure ##
\--- com.android.support:appcompat-v7:18.0+ -> 18.0.0
     \--- com.android.support:support-v4:18.0.0

上記のようにappcompat-v7:18はsupport-v4に依存している事が分かります。

依存解決は通常ありがたいことなのですが、今回作成したプロジェクトではlibsフォルダ配下に既にsupport-v4が存在しています。そのため、excludeしないとビルドエラーとなってしまいます。(Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompatとか出ます。)

libs配下のsupport-v4を削除するとEclipseのビルドができなくなってしまいますので、今回は上記のように書いてありますが、Gradleでのビルド前提の場合にはGradleで依存解決は解決する方が妥当だと思います。

AndoridをGradleでビルドする

まず、自分がビルドしたいAndoridプロジェクトをサーバーに任意の場所にcloneします。

そして、build.gradleがある場所(対象のAndroidプロジェクトのRoot)に移動します。

そこで、build.gradleを開き、buildToolsVersionがサーバーでインストールしたバージョンと合っているか確認して下さい。もし、違う場合にはエラーとなるので、書き換えてください。

ANDROID_BUILD_TOOLSというような変数にしておいて、gradle.propertiesなどに設定しておく方法がおすすめです。

さて、ビルドしてみます。

$gradle build 

もし、特にエラーがなければ、build/outputs/apk配下にapkが作成されているはずです。

build.gradldを編集する2

buidl.gradleを編集してRobolectricでテスト出来るようにします。

各変更点は後述しますが、全体で以下のようになりました。

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.13.0'
        classpath 'org.robolectric:robolectric-gradle-plugin:0.13.0'
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
}

apply plugin: 'android'
apply plugin: 'robolectric'

android {
    compileSdkVersion 17
    buildToolsVersion "20.0.0"

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
        androidTest {
            setRoot('test')
        }

        // Move the tests to tests/java, tests/res, etc...
        //instrumentTest.setRoot('tests')

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        //release.setRoot('build-types/release')
    }
}

robolectric {
    include '**/*Test.class'
}

dependencies {

    compile fileTree(dir: 'libs', include: '*.jar')
    compile ('com.android.support:appcompat-v7:18.0+') {
      exclude module: 'support-v4'
    }

    // test
    androidTestCompile('junit:junit:4.11') 
    androidTestCompile('org.robolectric:robolectric:2.3')
}

変更点は以下の通りです。

  • 依存するプラグインとしてrobolectricを追加(8,19行目)
  • テストフォルダの位置を指定(35~37行目)
  • robolectricで実行するテストクラスを指定(53~55行目)
  • テストのコンパイル時に利用するライブラリを記述(65,66行目)

自分がこの中でも特にはまったのがテストフォルダの位置を指定する箇所です。

これについてなのですが、robolectric-gradle-plugin:0.12.0までは本設定をしてもまったく有効化せず、必ずsrc/test/java配下にファイルを配置しないといけませんでした。

ただ、0.13からは任意のディレクトリの指定が可能となりました。ただし、必ず指定したディレクトリ配下にjavaというフォルダがある必要はありますので、注意が必要です。

GradleからRobolectricを実行する

早速やってみます。

$gradle clean test

gradle1.12では下記エラーになってしまいます。。。

* What went wrong:
A problem occurred evaluating root project 'RobolectricGradleSample'.
> Could not create plugin of type 'AppPlugin'.

ただし、Gradle2.1ではうまくできます。

$gvm install gradle 2.1
$gvm use gradle 2.1
$gradle clean test

こんな感じで結果がbuild/test-report/index.htmlに出力されています。

また、build/test-result/にXMLも出力されるのでこれを利用してJenkinsで実行結果のグラフ表示も可能です。

RobolectricのPluginが1.x系に対応してないからなのでしょうか。。。ちょっといまいち理由が分かっていません。知っている人がいらっしゃったら教えて頂けるとありがたいです。

2014年7月2日水曜日

[Android]CLIからインテントの送信、ブロードキャストの送信を行ってみた

このエントリーをはてなブックマークに追加 はてなブックマーク - [Android]CLIからインテントの送信、ブロードキャストの送信を行ってみた

Androidのアプリで違うアプリから自分のアプリがkickされることって良くあると思います。

で、その時の動作を確認する為に、スタブアプリを作ってーみたいな事を今までやっていたのですが、実はCLIでインテントの送信やブロードキャストの送信も出来る事が分かり、とても便利なのでそのメモ。

参考

ターミナルからIntentを投げる

Broadcastをエミュレートする

前提

  • テストするアプリのパッケージ名はcom.example.toshihirock.myapplicationとしています。適宜自分の環境に置き換えて読んでください。
  • MyActivityというActivityが存在するものとします。
  • adb devicesで端末が認識出来る状態にしておきます。

明示的インテント

$adb shell am start -n 'com.example.toshihirock.myapplication/.MyActivity'

com.example.toshihirock.myapplicationというパッケージ名のMyActivityを起動させます。

アプリ終了

$adb shell am force-stop com.example.toshihirock.myapplication

パッケージ名を指定します。

暗黙的インテント

$adb shell am start -a 'android.intent.action.VIEW' -d 'http://google.co.jp'

-dではDATA_URIを指定しています。

ブロードキャストの送信

今回はom.example.toshihirock.testというアクションでtestという文字列の変数を送る事を確認してみます。

以下のようなクラスがあったとしてます。

MyBroadCastReceiver.java

package com.example.toshihirock.myapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * Created by toshihirock on 7/2/14.
 */
public class MyBroadCastReceiver extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        String str = intent.getExtras().getString("test");
        Log.i("tag",str);
    }

}

また、AndroidManifest.xmlでreceiverに上記クラスを設定します。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.toshihirock.myapplication" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MyActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name="MyBroadCastReceiver">
            <intent-filter>
                <action android:name="com.example.toshihirock.test"></action>
            </intent-filter>
        </receiver>
    </application>

</manifest>

上記作成後、以下のコマンドで該当のクラスへのBroadcastが確認出来ます。

$adb shell am broadcast -a 'com.example.toshihirock.test' -e 'test' 'aaaa'

Logcatには以下のように出力されるはずです。

07-01 11:40:37.621    3144-3144/com.example.toshihirock.myapplication I/tag﹕ aaaa

なお、文字列に"などを指定したい場合には、以下のようにエスケープする事で正しく内容が送信出来ます。

$adb shell am broadcast -a 'com.example.toshihirock.test' -e 'test' '\"aaaa\"'

Logcatで確認出来ます。

07-01 12:02:30.007    3144-3144/com.example.toshihirock.myapplication I/tag﹕ "aaaa"

その他

以下のコマンドでヘルプが見れるので詳細はこちらを見ると良いかと思います。

$adb shell am

他にも色々出来るっぽいです。