しかし、テストとなるとあまり情報が多くありません。
というわけで、
簡単な
ActivityInstrumentationTestCase2<T extends Activity>
の書き方を記事にしました。
今回のテスト対象のアプリケーションはこんな感じです。
まあ、百聞は一見にしかずなので、画面イメージ出しましょう。
これが最初の画面です。
そして、これに文字を入れて、ボタンを押すとこうなります。
では、早速テストを書きましょう。
まずは、コンストラクターと
setUp()
から。
class HelloAndroidTest
extends ActivityInstrumentationTestCase2<HelloActivity>{
private Activity activity;
private Instrumentation instrumentation;
public HelloAndroidTest(){
super(HelloActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
activity = getActivity();
instrumentaion = getInstrumentation();
setActivityInitialTouchMode(false);
}
}
まず、ここのポイントは
- コンストラクター
setUp()
中にあるActivityInstrumentationTestCase2
#getActivity()
- 同様に
setUp()
中にあるActivityInstrumentationTestCase2
#getInstrumentation()
コンストラクターではテスト対象のアクティビティ・クラスを
ActivityInstrumentationTestCase2
に渡してやります。「総称型(ジェネリクス)でクラスを指定しているんだから、なんでわざわざクラスを通知してやる必要があるの?」という声も聞こえそうですね。すこしだけ解説すると、タイプパラメーター<T>というのは、ただただタイプとして機能するだけで、java.lang.Class<?>
オブジェクトのような機能や、役割を一切持ちません。したがって、
Class clz = T.class;
とか、
boolean isInstance = object instanceof T;
とか、
T object = T.newInstance();
などはすべてコンパイルエラーになります。
これよりも詳しい説明は私が説明するよりも他にもっと優れた解説があります。Java総称型メモなどを参考にしてください。
話を元に戻すと、
ActivityInstrumentationTestCase2
で、テスト対象のアクティビティを起動するために、そのクラスの情報が必要なわけですね。さて、これジェネリクスで指定したクラスと異なるクラスを渡したらどうなるのか?わかりませんねぇ。あとでやってみましょう。まぁ、予想できる結果として、この後の解説で述べる#getActivity()
でjava.lang.ClassCastException
が発生するように思います。setUp()
中にあるActivityInstrumentationTestCase2
#getActivity()
はActivityを起動して、参照を返すメソッドです。これにより、テストコードはテスト対象のアクティビティをそのコンテクスト上で起動することが可能になります。テストはそれ自体でひとつのアプリケーションですが、テストアプリケーションとは別のスレッド上にテスト対象のアプリケーションが起動するのです。これをsetUp()
に置くことで、毎回のテストがアクティビティを起動した状態で実行できます。最後に、
setUp()
中にあるActivityInstrumentationTestCase2
#getInstrumentation()
です。このInstrumentation
は、アクティビティの起動とは何の関係もありませんが、アクティビティのUIスレッドと同期をとるような場合や、UIに対して操作を行う場合に必要になるオブジェクトです。今回のテストアプリケーションでも使いますので、最初にその参照を取得しておきます。では、テストコードをどうぞ。
public void testPressButton() {
// EditText にフォーカスを当てる ---- (1)
EditText editText = (EditText)activity.findViewById(
orz.mikeneck.hello.R.id.edit_text);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
editText.requestFocus();
}
});
// UIとの同期をはかる ---- (2)
instrumentation.waitForIdleSync();
// UIにキーを送る ---- (3)
sendKeys(KEYCODE_SHIFT_LEFT);
sendKeys(KEYCODE_A);
sendKeys(KEYCODE_N);
sendKeys(KEYCODE_D);
sendKeys(KEYCODE_R);
sendKeys(KEYCODE_O);
sendKeys(KEYCODE_I);
sendKeys(KEYCODE_D);
// 前提条件の確認 ---- (4)
assertEquals("Android", editText.getText().toString());
// Button をクリックする ---- (5)
Button button = (Button)activity.findViewById(
orz.mikeneck.hello.R.id.button);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
button.performClick();
}
});
// UIとの同期をはかる ---- (6)
mInstrumentaion.waitForIdleSync();
// TextView の値をテストする ---- (7)
TextView textView = (TextView)findViewById(
orz.mikeneck.hello.R.id.edit_text);
assertEquals("Hello,Android", textView.getText());
}
大雑把な解説ですが、
EditText
にフォーカスを当てます。これはUI上の操作に当たるので、Activity
#runOnUiThread(java.lang.Runnable)
を使用します。- UIとの同期をはかります。別スレッド(アクティビティ)との同期は
Instrumentation
waitForIdleSync()
を用います。 - UIにキーを送ります。
android.view.KeyEvent
をstatic importしておいてください。 EditText
にキーを送ったので、それが反映されていることをテストします。想定される値は「Android」です。Button
をクリックします。これはUI上の操作に当たるので、Activity
#runOnUiThread(java.lang.Runnable)
を使用します。- UIとの同期をはかります。別スレッド(アクティビティ)との同期は
Instrumentation
waitForIdleSync()
を用います。 Button
をクリックしたので、仕様通りにTextView
のテキストが変更されているかテストします。想定される値は「Hello,Android」です。
ポイントは
- UI上の操作は必ず
Activity
#runOnUiThread(java.lang.Runnable)
を使用する。 - UI操作後は
Instrumentation
waitForIdleSync()
を用いる。
#runOnUiThread(java.lang.Runnable)
を使わないとUIが操作できないのは、他のアプリケーションからいくらでも値を変更するなどの改変が行われてしまうからというセキュリティ的な側面があるのかと思われます。(Javaそんなに詳しいわけではないので、そのあたりを突っ込んでくれる人、大募集)二番目の
Instrumentation
waitForIdleSync()
がActivityInstrumentationTestCase2
の真骨頂です。UIスレッドとの同期をはかりスムーズにテストを実行出来るようなオブジェクト類が提供されており、それがActivityInstrumentationTestCase2
です。別スレッドのUIと同期するこの能力はテストの実行においてかなり強力な機能です。こんな感じで、非常に簡単なテストでしたが、みなさまもUI上で不具合を発見したら、なるべくテストコードを書いて、それらを再現できるようにしてみてください。きっと、素晴らしいUIをもつアプリケーションが作成できると思います。
0 件のコメント:
コメントを投稿