2013年11月21日木曜日

Android UI Testing framework の Espresso を使う

とりあえず、android-test-kit : Espresso の動画を見ましょう。

以下では Eclipse での設定方法を紹介します。
Android Studio での設定方法は Espresso のプロジェクトページ(上記のリンク)にあるので読んでください。

1. Developer options の設定

アニメーションを Off にしましょう。

設定(Settings) → 開発者向けオプション(Developer options)→
以下の3つを全て「アニメーションオフ(Animation off)」にする
  • ウィンドウアニメスケール (Window animation scale)
  • トランジションアニメスケール(Transition animation scale)
  • Animator再生時間スケール(Animator duration scale)


コードからやる方法


2. Espresso をテストプロジェクトに追加する

Espresso には、依存ライブラリとかも含めて1つの jar になっている standalone 版と、依存ライブラリが別になっている dependencies 版があります。

mockito と一緒に使う場合は、hamcrest がかぶってエラーになるので、dependencies 版を使います。

standalone 版を使う場合:git clone するなり、zip をダウンロードするなりして、 espresso-1.0-SNAPSHOT-bundled.jar を取得して、テストプロジェクトの libs フォルダに追加します。

dependencies 版を使う場合: dependencies 版 にある jar を全部 libs フォルダに入れます。
mockito (mockito-all-1.9.5.jar) と一緒に使う場合は、hamcrest-core-1.1.jar と hamcrest-integration-1.1.jar は libs に入れないでください。



テストプロジェクトの AndroidManifest.xml に
  1. <instrumentation  
  2.   android:name="com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"  
  3.   android:targetPackage="$YOUR_TARGET_APP_PACKAGE"/>  
を追加します。 AndroidManifest.xml の例
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.example.espresso.test"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="8"  
  9.         android:targetSdkVersion="19" />  
  10.   
  11.     <application>  
  12.         <uses-library android:name="android.test.runner" />  
  13.     </application>  
  14.   
  15.     <instrumentation  
  16.         android:name="com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"  
  17.         android:targetPackage="com.example.espresso" />  
  18.   
  19. </manifest>  
ウィザードから Android Test Project を作ると、android.test.InstrumentationTestRunner の instrumentation タグが作られますが、消しても大丈夫です。
  1. <instrumentation  
  2.     android:name="android.test.InstrumentationTestRunner"  
  3.     android:targetPackage="com.example.espresso" />  


GoogleInstrumentationTestRunner を介してテストが走るように Eclipse を設定します。
Eclipse の [Run] - [Run Configurations...]を選択



Run all tests in the selected project, or package にチェックして、 Instrumetation runner: に com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner を選択して Apply をクリックします。



* クラス単体を対象とした場合(Run a single Test をチェック)、Instrumetation runner: に com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner を選択すると、 The instrumentation runner must be of type android.test.InstrumentationTestRunner とエラーが出て怒られます。
Espresso ではクラス単体でテストを走らせることはできないってことなのかしら?

Espresso はいくつかの解析データを収集しています。
収集されたくない場合は、disableAnalytics という引数に true を指定して GoogleInstrumentationTestRunner に渡すことでオプトアウトすることができるとドキュメントには書いてあるのですが、方法がよくわかりませんでした。。。


3. Espresso を使う

例として、ログイン画面(MainActivity)でIDとパスワードを入力してボタンを押すと、Welcome画面(MainActivity2)に遷移するアプリを用意しました。
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.   
  8.         final EditText idView = (EditText) findViewById(R.id.editText1);  
  9.         final EditText passView = (EditText) findViewById(R.id.editText2);  
  10.         final TextView statusView = (TextView) findViewById(R.id.textView3);  
  11.   
  12.         findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {  
  13.   
  14.             @Override  
  15.             public void onClick(View v) {  
  16.                 statusView.setText("");  
  17.                 String id = idView.getText().toString();  
  18.                 if (TextUtils.isEmpty(id)) {  
  19.                     statusView.setText("IDが入力されていません");  
  20.                     return;  
  21.                 }  
  22.                 String pass = passView.getText().toString();  
  23.                 if (TextUtils.isEmpty(pass)) {  
  24.                     statusView.setText("Passwordが入力されていません");  
  25.                     return;  
  26.                 }  
  27.   
  28.                 if (check(id, pass)) {  
  29.                     Intent intent = new Intent(MainActivity.this, MainActivity2.class);  
  30.                     intent.putExtra("id", id);  
  31.                     startActivity(intent);  
  32.                 } else {  
  33.                     statusView.setText("IDとPasswordの組み合わせが違います");  
  34.                 }  
  35.             }  
  36.         });  
  37.     }  
  38.   
  39.     boolean check(String id, String pass) {  
  40.         // dummy  
  41.         return true;  
  42.     };  
  43. }  
ログイン画面(MainActivity)では、IDやパスワードが空の場合はステータス用のTextViewにメッセージが表示されます。
つまり
・ID入力用のEditTExtが空のときにステータス用のTextViewにメッセージが表示されるか
・Password入力用のEditTextが空のときにステータス用のTextViewにメッセージが表示されるか
をテストできます。



ログインできる場合は、Intentのextraにidを入れて、Welcome画面(MainActivity2)を開いています。
  1. public class MainActivity2 extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main2);  
  7.           
  8.         String id = getIntent().getStringExtra("id");  
  9.         TextView tv = (TextView) findViewById(R.id.textView1);  
  10.         tv.setText("ようこそ" + id + "さん");  
  11.     }  
  12. }  
Welcome画面(MainActivity2)では、渡されたidをTextView に表示しています。
ここでは、
・ログイン画面で入力されたIDがWelcome画面に表示されるか
をテストできます。




では、テストクラスを作っていきます。

Espresso, ViewActions, ViewMatchers, ViewAssertions, Matchers などの主要 static メソッドを import static で定義しておきましょう。
Espresso のドキュメントに載っているサンプルコードはみな import static した後のコードです。そのことを知ってないとコードをみてもよくわからないでしょう。
ドキュメントのコードをコピペするときにも不便なので、以下の import static をテストクラスにコピペしておきましょう。

  1. import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;  
  2. import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;  
  3. import static com.google.android.apps.common.testing.ui.espresso.Espresso.pressBack;  
  4. import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;  
  5. import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.closeSoftKeyboard;  
  6. import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;  
  7. import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;  
  8. import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;  
  9. import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;  
  10. import static org.hamcrest.Matchers.allOf;  
  11. import static org.hamcrest.Matchers.containsString;  
  12. import static org.hamcrest.Matchers.instanceOf;  
  13. import static org.hamcrest.Matchers.is;  


Espresso は Activity を起動してくれないので、setUp() で getActivity() を呼んで Activity を起動する必要があります。

  1. package com.example.espresso;  
  2.   
  3. import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;  
  4. import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;  
  5. import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;  
  6. import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;  
  7. import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;  
  8. import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;  
  9. import android.test.ActivityInstrumentationTestCase2;  
  10.   
  11. public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {  
  12.   
  13.     public MainActivityTest() {  
  14.         super(MainActivity.class);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void setUp() throws Exception {  
  19.         super.setUp();  
  20.         // Espresso will not launch our activity for us, we must launch it via  
  21.         // getActivity().  
  22.         getActivity();  
  23.     }  
  24.       
  25.     public void testEmptyId() {  
  26.         // IDを空のままログインボタンをクリック  
  27.         onView(withId(R.id.button1)).perform(click());  
  28.         // ステータス用のTextViewにメッセージが表示されるかチェック  
  29.         onView(withId(R.id.textView3)).check(matches(withText("IDが入力されていません")));  
  30.     }  
  31.   
  32.     public void testEmptyPassword() {  
  33.         // IDを入力  
  34.         onView(withId(R.id.editText1)).perform(typeText("yanzm"));  
  35.         // Passwordを空のままログインボタンをクリック  
  36.         onView(withId(R.id.button1)).perform(click());  
  37.         // ステータス用のTextViewにメッセージが表示されるかチェック  
  38.         onView(withId(R.id.textView3)).check(matches(withText("Passwordが入力されていません")));  
  39.     }  
  40.       
  41.   
  42.     public void testLogin() {  
  43.         // IDを入力  
  44.         onView(withId(R.id.editText1)).perform(typeText("yanzm"));  
  45.         // Passwordを入力  
  46.         onView(withId(R.id.editText2)).perform(typeText("1234567890"));  
  47.         // ログインボタンをクリック  
  48.         onView(withId(R.id.button1)).perform(click());  
  49.         // Welcome画面に表示されるかチェック  
  50.         onView(withId(R.id.textView1)).check(matches(withText("ようこそyanzmさん")));  
  51.     }  
  52. }  


こんな感じです。

他にも、ListView や Spinner などの特定の行の View を指定するために使う onData() などがあります。


参考




0 件のコメント:

コメントを投稿