背景
これまで、EspressoのUIテストにおけるネットワーク通信待機処理をThread.sleep()で行なっていました。
この方法では、通信が完了した後も指定した時間待つことになり、テスト実行に余計な時間が掛かってしまいます。
これを改善するために、今回はUiAutomatorを利用して指定したViewが表示されたタイミングで待機処理を終了する実装に置き換えました。
実装
まずは、UiAutomatorの依存関係を追加します。
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
そして、Kotlinの拡張関数を用いてトップレベル関数として待機処理を実装します。
テスト設計にはPageObjectパターンを適用しているため、戻り値にはthisを指定してレシーバを返すようにしています。
※PageObjectパターンはDroidKaigi2019のこちらのセッションを参考にさせていただきました。(https://droidkaigi.jp/2019/timetable/70791/ )
/**
* [resId]に指定したViewが表示されるまで待つ
* [timeout]はデフォルトで7秒
*/
fun <T : Any> T.waitShown(resources: Resources, @IdRes resId: Int, timeout: Long = 7_000): T {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.wait(Until.findObject(By.res(
"[YOUR_APP_PACKAGE_NAME]",
resources.getResourceEntryName(resId))),
timeout)
return this
}
テストメソッドで使うとこのようになります。
画面の挙動としては、SampleActivityで通信を行い、通信中はローディングが表示され、通信完了後にtarget_viewが表示されるイメージです。
@RunWith(AndroidJUnit4::class)
@LargeTest
class SampleActivityTest {
@Rule
@JvmField
val activityRule: ActivityTestRule<SampleActivity> = ActivityTestRule(SampleActivity::class.java)
@Test
fun test() {
val resources = activityRule.activity.resources
SampleActivityPage
.fetchData() // 時間の掛かる通信処理
.waitShown(resources, R.id.target_view) // `target_view`が表示されるまで待機
.assert() // 任意のアサーション
}
}
解説
waitShownの処理を分解して見てみます。
/**
* [resId]に指定したViewが表示されるまで待つ
* [timeout]はデフォルトで7秒
*/
fun <T : Any> T.waitShown(resources: Resources, @IdRes resId: Int, timeout: Long = 7_000): T {
// デバイスの状態を提供するクラス。ホームボタンなどの制御も可能。
val uiDevice: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// UI要素を指定するクラス。パッケージ名とリソースID名で指定する。
val bySelector: BySelector = By.res("[YOUR_APP_PACKAGE_NAME]", resources.getResourceEntryName(resId))
// waitメソッドに渡す条件。最初に一致するUI要素(BySelector)を条件として返す。
val searchCondition: SearchCondition<UiObject2> = Until.findObject(bySelector)
// 条件(SearchCondition)に一致するか、タイムアウトになるまで待機する。
uiDevice.wait(searchCondition, timeout)
return this
}