LoginSignup
62

More than 5 years have passed since last update.

(Android) 知らない人が作ったアプリのテストを書く

Last updated at Posted at 2015-10-04

UI Automatorで他人のアプリのテストを書きました

最近,あるAndroidアプリで「"pink"と検索するとクラッシュする」
ということに気がつきました.

crash_cap.png

そこで,クラッシュするシナリオを再現するテストを書いて,
そのテストコードと共にバグ報告を行いました.

ということで今回は,
UI AutomatorとUI Automator Viewerを使って,
ソースコードが見れないアプリに対して,テストを書く方法について書きます.

UI Automatorのセットアップ

UI Automatorは,minSDKVersionが18以上のプロジェクトでしか使えないので,
minSDKVersionが18以上のプロジェクトを用意します.
そして,app配下のbuild.gradleに以下のコードを追加します.

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'
}

では,本題に入ります.

クラッシュを再現するテストコードを書く

今回,再現したいシナリオはこうです.

前処理 : ホームスクリーンからアプリを起動しておく
1. サーチアクションボタンをクリック
2. 検索窓に"pink"と入力してEnterを押す
3. 遷移後の画面でアプリがクラッシュ

上のシナリオを再現しようとすると,以下のようなコードになります.

start_target_app()でアプリを開く前処理を行い,
search_pink_and_crash()でテストしたいシナリオをシミュレートします.

androidTest/java/com.ikota.uiautomatorsample.MyUiAutomatorTest.java
@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つ分からないことが出てきました.

  1. ソースの見れない状態で,操作したいUiObject(View)をどうやって指定するのか.
  2. クラッシュしたことの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が表示されます.

uiautomator_default.png

まず,テストで使いたいview(今回ならサーチボタン)をAndroid端末で表示された状態にして,端末をPCに接続しておきます.
次に,UI Automator Viewerの左上のアイコン列の,端から2番目にあるデバイスのアイコンをクリックします.
すると,次のように端末のスクリーンショットがUI Automator Viewer上に表示されます.

uiautomator_app_cap.png

window上のスクリーンショットのサーチアイコンをクリックすると,
画面右下の領域に,クリックしたviewのidやクラス名などのプロパティが表示されます.
同様に検索窓(search_field)のプロパティも調べることで,次のようにviewの指定ができるようになりました.

androidTest/java/com.ikota.uiautomatorsample.MyUiAutomatorTest.java
// 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で調べておき,
テストではダイアログが表示されたかどうかのアサーションを書く.というものです.

次のコードは,ダイアログに含まれるエラーメッセージを利用して,
クラッシュの有無をアサーションしています.

androidTest/java/com.ikota.uiautomatorsample.MyUiAutomatorTest.java
// 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を利用してテストを書いてみてはいかがでしょうか ?

それでは,今回のテストコードの完成形と参考リンクを載せて,締めさせて頂きます.

androidTest/java/com.ikota.uiautomatorsample.MyUiAutomatorTest.java
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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62