こんにちはmisocaアプリの評価が3.5になって嬉しいtijinsです。
今日はAndroidアプリのインストルメンテーションテストについて紹介します。
Misocaアプリのテスト
MisocaアプリはCIによる自動テストとリリース前の手動テストを行っているのですが、開発スピードと品質のバランスを保つ為、手動のテストの自動化を進めています。
3種類の自動テスト
Misocaアプリでは3種類の自動テストを使用しています。 今回は、インストルメンテーションテストについて説明します。
インストルメンテーション テスト
エミュレーターまたは実機上で実行されるテストです。 実際のアプリを動作させているので手動テストと同等の検証が可能ですが、実行速度が遅いです。
Unitテスト
開発PCのJVM上で実行されるテストです。 エミュレーターを介さない為、高速なテストが可能ですが、AndroidOSやContextに依存する処理はテストできません。 (Robolectricを使用すればAndroidOSに依存する部分もテスト可能ですが、非対応の機能がありインストルメンテーションテストの完全な代替には至っていません)
スクリーンショット比較テスト
レイアウトやテーマの異常を見つけるテストです。 インストルメンテーションテスト中にSpoonを使用して保存したスクリーンショット画像で、レイアウトの崩れなどをチェックしています。
サンプルアプリ
今回のテストコードが対象にしているのは、以下のサンプル用アプリです。
- メイン画面で編集ボタンを押すと、編集画面に遷移する
- 編集画面で保存ボタンを押すと、編集された文字列がメイン画面に反映される
- メイン画面で共有ボタンを押すと、IntentChooser付きで共有される
インストルメンテーションテスト
テスト対象アプリの操作にespressoを使用します。
espresso-intentsは、Activity間の遷移を検証したり、ファイル選択等外部のアプリと連携する部分のスタブ化に使用しています。
Espressoの導入手順 Espresso のセットアップ手順 | Android デベロッパー | Android Developers
Espresso-intents の導入手順 Espresso-Intents | Android デベロッパー | Android Developers
Espressoを使ったテスト
以下は、メイン画面から編集画面に遷移し、編集結果がメイン画面に反映される事を確認するサンプルです。
app/build.gradle
dependencies { // InstrumentTestで使用するライブラリ androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
SampleActivityTest.kt
@RunWith(AndroidJUnit4::class) class ExampleActivityTest { @get:Rule val activityRule = ActivityTestRule(MainActivity::class.java) @Test fun testEdit() { // メイン画面で実行される Espresso.onView(ViewMatchers.withId(R.id.btn_edit)).perform( ViewActions.click() ) // 編集画面で実行される // 初期表示の確認 Espresso.onView(ViewMatchers.withId(R.id.edit_text)).check( ViewAssertions.matches( ViewMatchers.withText("Hello") ) ) // EditTextに入力する Espresso.onView(ViewMatchers.withId(R.id.edit_text)).perform( ViewActions.replaceText("Bye!") ) // 保存ボタンをクリックする Espresso.onView(ViewMatchers.withId(R.id.btn_save)).perform( ViewActions.click() ) // メイン画面で実行される // 編集結果が反映されている Espresso.onView(ViewMatchers.withId(R.id.txt_text)).check( ViewAssertions.matches( ViewMatchers.withText("Bye!") ) ) } }
Espresso-intentsを使ったテスト
startActivity()
で送信されたIntentの検証や、ファイル選択など他のアプリと連携する部分のスタブ化が可能です。
送信されたIntentの検証(IntentChooserが無い場合)
app/build.gradle
dependencies { // InstrumentTestで使用するライブラリ androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' // import androidx.test.ext.truth.content.IntentSubject.assertThatに必要です androidTestImplementation 'androidx.test.ext:truth:1.3.0' // espresso-intents androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' // 戻るボタンの操作などに利用します androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' }
SampleIntentsTest.kt
@RunWith(AndroidJUnit4::class) class ExampleIntentsTest { // IntentTestRuleを使用します @get:Rule val activityRule = IntentsTestRule(MainActivity::class.java) // バックキーを操作する場合に必要です private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @Test fun testEdit() { Espresso.onView(ViewMatchers.withId(R.id.btn_edit)).perform( ViewActions.click() ) // getIntents()はactivityが起動されてから発行されたIntentのリストを返します val intents = Intents.getIntents() val intent = intents.first() assertThat(intent).hasComponent( ComponentName( "jp.misoca.sampleintentstest", "jp.misoca.sampleintentstest.EditActivity" ) ) // androidx.test.ext.truth.content.IntentSubject.assertThatを使用します assertThat(intent).extras().string(Intent.EXTRA_TEXT).isEqualTo("Hello") } }
送信されたIntentの検証(IntentChooserが有る場合)
IntentChooserでラップされたIntentでは
Intent.EXTRA_INTENT
に元のIntentが入っています。
@Test fun testShare() { Espresso.onView(ViewMatchers.withId(R.id.btn_share)).perform( ViewActions.click() ) // IntentChooserが使用されている val chooser = Intents.getIntents().first() assertThat(chooser).hasAction(Intent.ACTION_CHOOSER) // 元のIntentを取得する val intent = chooser.getParcelableExtra<Intent>(Intent.EXTRA_INTENT) assertThat(intent).hasAction(Intent.ACTION_SEND) assertThat(intent).hasType("text/plain") assertThat(intent).extras().string(Intent.EXTRA_TEXT).isEqualTo("Hello") // バックキーを操作してChooserを閉じる(Chooserを閉じるまで次のテストに遷移しない為) device.pressBack() }
startActivityForResult()をスタブ化する
indending()
使用するとstartActivityForResult()
の実行後`onActivityResult()`がすぐ実行されます。
画像選択など、他のパッケージに依存するテストに便利です。
@Test fun testOnEdit() { // onActivityResultに入力されるダミーデータ val resultData = Intent().apply { putExtra(Intent.EXTRA_TEXT, "Bye!") } val result = Instrumentation.ActivityResult(Activity.RESULT_OK, resultData) // intendingを使用して、編集画面をスタブ化する // androidx.test.espresso.intent.Intents.intendingを使用します intending( IntentMatchers.hasComponent( ComponentName( "jp.misoca.sampleintentstest", "jp.misoca.sampleintentstest.EditActivity" ) ) ).respondWith(result) // intendingされているので、編集画面が起動せずにonActivityResultが実行される Espresso.onView(ViewMatchers.withId(R.id.btn_edit)).perform( ViewActions.click() ) //結果をチェック Espresso.onView(ViewMatchers.withId(R.id.txt_text)).check( ViewAssertions.matches( ViewMatchers.withText("Bye!") ) ) }
宣伝
弥生モバイルチームは先月1名メンバーが増えて拡大中です。 AndroidもiOSのエンジニアまだまだ募集中です!! www.wantedly.com