/var/log/messages

Nov 7, 2013 - 5 minute read - Comments - android

今更ですが Activity Testing Tutorial をしてみた

手抜きしつつ以下を確認。

何がしたいかというと Espresso の動作確認だったりするのでさくっと進める方向にて。

以下から着手してます。超手抜き。

-Installing the Completed Test Application File

どうも SDK に添付されてるサンプルにあるナニのテスト、という形らしい。とりあえず API15 で云々する方向にて。先に import しといた方が良さげ。ADT15 な SpinnerActivity を File - New - Other - Android Project from Existing Code からワークスペースに取り込んでおきます。

テスツなプロジェクト作成

SpinnerActivity がワークスペースにあるのであれば以下から順に。

  • File - New Project - Android - Android Test Project を選択
  • とりあえず SpinnerActivityTest ってプロジェクトの名前を付けて next
  • Test Target として SpinnerActivity を選択して next
  • Build Target はそのままで finish

で良いのかな。一応試験の方の AndroidManifest.xml に以下な記述があることを確認。

    <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.android.example.spinner" />

ActivityInstrumentationTestCase2 を継承したクラスを作成とのこと。まだ src 配下にはパケジはあるもののクラスはありません。以下な手順か。

  • パケジを右クリックして New - Class を選択
  • New Java Class なダイアログにて
    • SpinnerActivityTest という名前
    • ActivityInstrumentationTestCase2 を superclass に指定
    • android.test.ActivityInstrumentationTestCase2 に手直し

finish するとソースが開いてコンパイルエラーが出てるので SpinnerActivity を解決するよう、import を追加。 あと、コンストラクタが無い、って文句を言われているのですが以下を追加する模様。

public SpinnerActivityTest() {
    super("com.android.example.spinner", SpinnerActivity.class);
}

げ、deprecated て言われるんスけどorz む、Android unit test, super deprecated, replacement? によれば以下で良い模様。

public SpinnerActivityTest() {
    super("com.android.example.spinner", SpinnerActivity.class);
}

つうか http://d.android.com/ はちゃんと記述を refine すべきだな (を そりゃ良いとして次に仕込むのは setUp() な模様。以下な属性を追加して

private SpinnerActivity mActivity;
private Spinner mSpinner;
private SpinnerAdapter mPanetData;

以下な setUp() を追加。

@Override
protected void setUp() throws Exception {
    super.setUp();

    setActivityInitialTouchMode(false);

    mActivity = getAtivity();

    mSpinner = (Spinner) mActivity.findViewById(
                       com.android.example.spinner.R.id.Spinner01);

    mPlanetData = mSpinner.getAdapter();

} // end of setUp() method definition

あと、import で解決できない型を fix する必要があります。これで setUp() は完成とのこと。で、この Tutorial では以下な試験を、との案内が最初にありました。

  • initial condition test
  • UI test
  • state management test

順番に実装していく模様です。

initial condition test の追加

これ、試験するにあたってその準備ができているかを確認する試験のようです。ここではテストなクラスの属性の値を確認しています。定義が以下。

public void testPreConditions() {
    assertTrue(mSpinner.getOnItemSelectedListener() != null);
    assertTrue(mPlanetData != null);
    assertTrue(mPlanetData.getCount(), ADAPTER_COUNT);
} // end of testPreConditions() method definition

あと、ADAPTER_COUNT の定義を追加。

public static final int ADAPTER_COUNT = 9;

追加したは良いのですが Call requires API level 8 とか言われてます。おそらく minSdkVersion あたりなのかどうか。確認してみるに

<uses-sdk android:minSdkVersion="3"
  android:targetSdkVersion="11" />

てなっててアレ。両方 15 にしては駄目なのかな Requires a minimum platform version of Android-3 (SDK 1.5) to run とか書いてあって微妙。 試験される側のソースを見てないのでアレですがそのまま続行。

UI test の追加

UI test ってことなので Espresso で置きかえるのはここになるのだろうか。とりあえず以下を追加。

    public void testSpinnerUI() {

        mActivity.runOnUiThread(
            new Runnable() {
                public void run() {
                    mSpinner.requestFocus();
                    mSpinner.setSelection(INITIAL_POSITION);
                } // end of run() method definition
            } // end of anonymous Runnable object instantiation
            ); // end of invocation of runOnUiThread

        this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
        for (int i = 1; i <= TEST_POSITION; i++) {
            this.sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
        } // end of for loop

        this.sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);

        mPos = mSpinner.getSelectedItemPosition();
        mSelection = (String)mSpinner.getItemAtPosition(mPos);
        TextView resultView =
                (TextView) mActivity.findViewById(
                        com.android.example.spinner.R.id.SpinnerResult
                        );

        String resultText = (String) resultView.getText();

        assertEquals(resultText,mSelection);

    } // end of testSpinnerUI() method definition

あまり深くは追及しないですが、runOnUiThread の中の run で準備してからイベントを起こしている模様。とりあえず spinner で選択してその結果を確認しているのは分かります。

state management tests の追加

本題は Espresso なのでどんどん進めます。Activity の状態遷移なイベントでの挙動確認、と見てるのですがどうなるか。

public void testStateDestroy() {
    mActivity.setSpinnerPosition(TEST_STATE_DESTROY_POSITION);
    mActivity.setSpinnerSelection(TEST_STATE_DESTROY_SELECTION);

    mActivity.finish();
    mActivity = this.getActivity();

    int currentPosition = mActivity.getSpinnerPosition();
    String currentSelection = mActivity.getSpinnerSelection();

    assertEquals(TEST_STATE_DESTROY_POSITION, currentPosition);
    assertEquals(TEST_STATE_DESTROY_SELECTION, currentSelection);
} // end of testStateDestroy() method definition

@UiThreadTest
public void testStatePause() {
    Instrumentation mInstr = this.getInstrumentation();

    mActivity.setSpinnerPosition(TEST_STATE_PAUSE_POSITION);
    mActivity.setSpinnerSelection(TEST_STATE_PAUSE_SELECTION);

    mInstr.callActivityOnPause(mActivity);

    mActivity.setSpinnerPosition(0);
    mActivity.setSpinnerSelection("");

    mInstr.callActivityOnResume(mActivity);

    int currentPosition = mActivity.getSpinnerPosition();
    String currentSelection = mActivity.getSpinnerSelection();

    assertEquals(TEST_STATE_PAUSE_POSITION,currentPosition);
    assertEquals(TEST_STATE_PAUSE_SELECTION,currentSelection);
} // end of testStatePause() method definition

finish させたり、あるいは明示的に onPause とか onResume とか呼び出してますね。

試験実行

早速実行してみたら red だ。testSpinnerUI() が失敗している模様。面倒なのでスルーしようかな。ここを Espresso で書きかえてみたりして。 とりあえず、以下から espresso-1.0-SNAPSHOT-bundled.jar を取得。

で、これを libs にアレして色々変更せよ、とありますね。む、以下を見て進めた方がよさげ。

で、Manifest を以下に、とある。

<instrumentation
    android:name="com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"
    android:targetPackage="$YOUR_TARGET_APP_PACKAGE" />

なるほど。で、Run - Run Configurations から GITR を選べ、とのこと。ちなみにこれらを盛り込んで testSpinnerUI() を空にしたら試験 green でした。 ええと、以下で記述しろ、というナニがあります。

  • Find (find all relevant controls for the test)
  • Ation (perform actions on some of the controls)
  • Check (verify that the actions had the desired outcome on the UI)

なので spinner を初期設定して動かしてみてチェックすれば良いのかな。とりあえず以下を setUp() に盛り込んでみたのですが

/*
    mSpinner =
            (Spinner) mActivity.findViewById(
                     com.android.example.spinner.R.id.Spinner01
                     );
*/
    mSpinner = onView(withId(com.android.example.spinner.R.id.Spinner01));

withId を知らぬ、と仰る。何故だ。

色々確認して

import とか参照の仕方とか修正。コンパイルエラーは取れたのですが動かぬ。

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers;
import com.google.android.apps.common.testing.ui.espresso.ViewInteraction;
import com.google.android.apps.common.testing.ui.espresso.action.ViewActions;
import com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions;

あるいは以下も No activities found. という RuntimeException でオチます。

    public void testSpinnerUI() {
        ViewInteraction spinner = onView(ViewMatchers.withId(com.android.example.spinner.R.id.Spinner01));
        ViewInteraction textview = onView(ViewMatchers.withId(com.android.example.spinner.R.id.SpinnerResult));

        textview.check(ViewAssertions.matches(ViewMatchers.withText("Mercury")));
    } // end of testSpinnerUI() method definition

むむむ、動く試験なプロジェクトを作ることはできているはずなので、ハロワを Espresso で試験するプロジェクトを作ってみるか。

ハロワ篇

ハロワなソレはでっちあげ、テスツなプロジェクトもできました。

  • AndroidManifest.xml の instrumentation 修正
  • libs 作って jar をコピィ
  • src 配下に ActivityInstrumentationTestCase2 を継承したクラスを追加
  • コンストラクタ追加
  • 試験追加
  • Run - Run COnfiguration で instrumentation runner を指定
  • 実行

動かない。ログが以下。

E/Trace(13560): error opening trace file: No such file or directory (2)
I/dalvikvm(13560): Could not find method com.google.android.apps.common.testing.testrunner.ExposedInstrumentationApi.execStartActivity, referenced from method com.google.android.apps.common.testing.testrunner.GoogleInstrumentation.execStartActivity
W/dalvikvm(13560): VFY: unable to resolve virtual method 228: Lcom/google/android/apps/common/testing/testrunner/ExposedInstrumentationApi;.execStartActivity (Landroid/content/Context;Landroid/os/IBinder;Landroid/os/IBinder;Landroid/app/Activity;Landroid/content/Intent;I)Landroid/app/Instrumentation$ActivityResult;
D/dalvikvm(13560): VFY: replacing opcode 0x75 at 0x001d
I/GoogleInstr(13560): Instrumentation Started!
I/GoogleInstr(13560): IntentSpyImpl not loaded: com.google.android.apps.common.testing.intento.IntentSpyImpl
I/GoogleInstrTest(13560): Test Started!
I/dalvikvm(13560): Failed resolving Landroid/support/v4/media/TransportMediatorJellybeanMR2; interface 63 'Landroid/media/RemoteControlClient$OnGetPlaybackPositionListener;'
W/dalvikvm(13560): Link of class 'Landroid/support/v4/media/TransportMediatorJellybeanMR2;' failed
W/ClassPathPackageInfoSource(13560): Cannot load class. Make sure it is in your apk. Class name: 'android.support.v4.media.TransportMediatorJellybeanMR2'. Message: android.support.v4.media.TransportMediatorJellybeanMR2
W/ClassPathPackageInfoSource(13560): java.lang.ClassNotFoundException: android.support.v4.media.TransportMediatorJellybeanMR2

なんとかなりそげですが時間切れ。