UI Automatorで他人のアプリのテストを書きました
最近,あるAndroidアプリで「"pink"と検索するとクラッシュする」
ということに気がつきました.
そこで,クラッシュするシナリオを再現するテストを書いて,
そのテストコードと共にバグ報告を行いました.
ということで今回は,
UI AutomatorとUI Automator Viewerを使って,
ソースコードが見れないアプリに対して,テストを書く方法について書きます.
UI Automatorのセットアップ
UI Automatorは,minSDKVersionが18以上のプロジェクトでしか使えないので,
minSDKVersionが18以上のプロジェクトを用意します.
そして,app配下のbuild.gradleに以下のコードを追加します.
android {
...
defaultConfig {
applicationId "com.ikota.uiautomatorsample"
minSdkVersion 18
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
...
androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test:rules:0.3'
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
}
では,本題に入ります.
クラッシュを再現するテストコードを書く
今回,再現したいシナリオはこうです.
前処理 : ホームスクリーンからアプリを起動しておく
- サーチアクションボタンをクリック
- 検索窓に"pink"と入力してEnterを押す
- 遷移後の画面でアプリがクラッシュ
上のシナリオを再現しようとすると,以下のようなコードになります.
start_target_app()
でアプリを開く前処理を行い,
search_pink_and_crash()
でテストしたいシナリオをシミュレートします.
@RunWith(AndroidJUnit4.class)
public class MyUiAutomatorTest extends InstrumentationTestCase {
private static final String APP_PACKAGE = "com.target.app";
private UiDevice mDevice;
private Intent createLaunchIntent(Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(APP_PACKAGE);
assertNotNull("Target app is not found on your device", intent);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
@Before
public void start_target_app() throws Exception {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
// Initialize UiDevice instance and go Home screen
mDevice = UiDevice.getInstance(instrumentation);
mDevice.pressHome();
// start target Activity
Context context = instrumentation.getTargetContext();
context.startActivity(createLaunchIntent(context));
mDevice.wait(Until.hasObject(By.pkg(APP_PACKAGE).depth(0)),5000);
}
@Test
public void search_pink_and_crash() throws Exception {
// 1. Click search button and expand search field
UiObject search_btn = ... // How to get target view ???
search_btn.click();
// 2. Input search keyword "pink" and go result screen
UiObject search_field = ... // How to get target view ???
search_field.setText("pink");
mDevice.pressEnter();
// 3. Wait a moment, then app crashes
// How to detect crash ???
}
}
ここで,コードを書いてみて2つ分からないことが出てきました.
- ソースの見れない状態で,操作したいUiObject(View)をどうやって指定するのか.
- クラッシュしたことのassertionをどうやって書くのか.
これらの問題は,UI Automator Viewerを使うことで解決できます.
#UI Automator Viewerで操作したいviewの情報を見る
UI Automator Viewerとは,AndroidSDKに含まれているツールで
.../android-sdks/tools/uiautomatorviewer
に置いてあります.
それでは実際に,UI Automator Viewerを使ってみます.
$ cd .../android-sdks/tools
$ ./uiautomatorviewer
上のコマンドでUI Automator Viewerを起動すると,以下のようなwindowが表示されます.
まず,テストで使いたいview(今回ならサーチボタン)をAndroid端末で表示された状態にして,端末をPCに接続しておきます.
次に,UI Automator Viewerの左上のアイコン列の,端から2番目にあるデバイスのアイコンをクリックします.
すると,次のように端末のスクリーンショットがUI Automator Viewer上に表示されます.
window上のスクリーンショットのサーチアイコンをクリックすると,
画面右下の領域に,クリックしたviewのidやクラス名などのプロパティが表示されます.
同様に検索窓(search_field
)のプロパティも調べることで,次のようにviewの指定ができるようになりました.
// 1. Click search button and expand search field
UiObject search_btn = mDevice.findObject(new UiSelector()
.resourceId("com.target.app:id/action_search"));
search_btn.click();
// 2. Input search keyword "pink" and go result screen
UiObject search_field = mDevice.findObject(new UiSelector()
.resourceId("com.target.app:id/search_src_text")
.className("android.widget.EditText"));
search_field.setText("pink");
mDevice.pressEnter();
//3. ...
#クラッシュしたかどうかのアサーションを書く
今回は,クラッシュの検知にもUI Automator Viewerを利用します.
方針は,クラッシュ時に出るダイアログのプロパティをUI Automator Viewerで調べておき,
テストではダイアログが表示されたかどうかのアサーションを書く.というものです.
次のコードは,ダイアログに含まれるエラーメッセージを利用して,
クラッシュの有無をアサーションしています.
// 3. detect crash by checking if error dialog is displayed.
UiObject error_message = mDevice.findObject(new UiSelector()
.textMatches("^Unfortunately,.*")
.resourceId ("android:id/message")
.className ("android.widget.TextView"));
// 4. Now, below assertion fails. Please fix it !!!
assertFalse("App crashed !!", error_message.exists());
#最後に
Androidアプリの実装に関わっていない人も,
UI Automatorを利用してテストを書いてみてはいかがでしょうか ?
それでは,今回のテストコードの完成形と参考リンクを載せて,締めさせて頂きます.
package com.ikota.uiautomatorsample;
// import ...
@RunWith(AndroidJUnit4.class)
public class MyUiAutomatorTest extends InstrumentationTestCase {
private static final String APP_PACKAGE = "com.target.app";
private UiDevice mDevice;
private Intent createLaunchIntent(Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(APP_PACKAGE);
assertNotNull("Target app is not found on your device", intent);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
@Before
public void start_target_app() throws Exception {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
// Initialize UiDevice instance and go Home screen
mDevice = UiDevice.getInstance(instrumentation);
mDevice.pressHome();
// start target Activity
Context context = instrumentation.getTargetContext();
context.startActivity(createLaunchIntent(context));
mDevice.wait(Until.hasObject(By.pkg(APP_PACKAGE).depth(0)),5000);
}
@Test
public void search_pink_and_crash() throws Exception {
// 1. Click search button and expand search field
UiObject search_btn = mDevice.findObject(new UiSelector()
.resourceId("com.target.app:id/action_search"));
search_btn.click();
// 2. Input search keyword "pink" and go result screen
UiObject search_field = mDevice.findObject(new UiSelector()
.resourceId("com.target.app:id/search_src_text")
.className("android.widget.EditText"));
search_field.setText("pink");
mDevice.pressEnter();
// 3. Wait a moment, then app crashes and error dialog is displayed.
SystemClock.sleep(5000); // In our case, we need to wait crash here
UiObject error_dialog = mDevice.findObject(new UiSelector()
.textMatches("^Unfortunately,.*")
.resourceId ("android:id/message")
.className ("android.widget.TextView"));
// 4. Now, below assertion fails. Please fix it !!!
assertFalse("App crashed !!", error_dialog.exists());
}
}
#参考URL