Espressoで非同期テストのやり方を学んだのでメモ
IdlingResource
Espressoで非同期な処理を使用したUIテストを実施するときは、安易にsleepとか使用せずに、IdlingResource
を使うことが推奨されているようです。
IdlingResource
自体はインタフェースなので、実際にはIdlingResource
を実装したクラスが必要になりますが、標準でいくつか用意されており今回は一番スタンダードなCountingIdlingResource
を使用します。
アクティブなタスクのカウンタを保持します。カウンタがゼロの場合、関連するリソースはアイドリング状態であると見なされます。この機能は、Semaphore の機能によく似ています。テスト中にアプリの非同期操作を管理するには、通常はこの実装で十分です。
アイドルリソース使用の流れ
アイドルリソース使用の流れは以下になります。
- テスト前にアイドルリソースを登録する
- 非同期処理開始前にアイドルリソースがビジーであることをセットする
- 非同期処理完了時点でアイドルリソースがアイドルであることをセットする
- テスト終了後にアイドルリソースを登録解除する。
一般的には、1は@Before
で実施し、4は@After
で実施するようです。
テスト設計
今回やりたいテストの流れについて設計します。
「ProgressBar」「include」「WebView」の各コンポーネントが初期表示時は「Progress」のみ表示されていて、ページ読み込み完了し、Webページが表示できる直前で「WebView」のみ表示されるようになるよね?といった部分を確認するテストになります。
今回は、このうち「ページ読み込み完了」という部分が非同期でいつ完了になるのかわかりません。よって、アイドルリソースを使って「読み込み完了したよ!」ということをEspressoに教えてあげます。
テスト実装
設計に基づいて実装をおこないます。
まず、テスト対象のフラグメントに手を加えます。
public class TosViewFragment extends Fragment implements AppWebViewClientCallback {
@VisibleForTesting
private final CountingIdlingResource countingIdlingResource
= new CountingIdlingResource("WebViewIdlingResource");
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public IdlingResource getCountingIdlingResource() {
return countingIdlingResource;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// ビジー状態
countingIdlingResource.increment();
// 省略
// Viewの返却
return binding.getRoot();
}
@Override
public void onPageCommitVisibleHandle(boolean isOk) {
// プログレスバーを非表示にする
binding.scTovPbProgressBar.setVisibility(View.GONE);
if (isOk) { // ページ表示可
// Webページを表示する
binding.scTovWvTos.setVisibility(View.VISIBLE);
} else {
// Sorryページを表示する
binding.scTovInSorry.setVisibility(View.VISIBLE);
}
// アイドル状態
countingIdlingResource.decrement();
}
}
👉 CountingIdlingResource
のインスタンスを対象のフラグメントに生成します。(ゲッターとかはテストクラスからしか呼ばれないので@VisibleForTesting(otherwise = VisibleForTesting.NONE)
をつけています。)
インスタンスを生成するときに、リソース名を引数にします。
つぎに、onCreateView
でビジー状態にしています。CountingIdlingResource
は内部でカウンタを持っており、ゼロになるとアイドル状態とみなされるので#increment
でカウンタをインクリメント(つまり、ビジー状態)にします。
最後に、onPageCommitVisibleHandle
(WebViewClient#onPageCommitVisible
が呼ばれた時のコールバック処理)の最後で#decrement
してあげて差し引き0(つまりアイドル状態)にしてあげます。
次にテストクラスに手を加えます。私はページオブジェクトが好きなので、ページオブジェクトで実装しています。
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ScT_ScTov {
public ActivityScenario<MainActivity> scenario;
private IdlingResource idlingResource;
@Before
public void setUp() {
// アクティビティの起動
scenario = ActivityScenario.launch(MainActivity.class);
}
@After
public void tearDown() {
// アイドルリソースの登録解除
IdlingRegistry.getInstance().unregister(idlingResource);
}
@Test
public void ScreenTest_ScTov() {
startScreenTest()
.isNotDisplayedScTovWbTos() // WebViewが表示されていないこと
.isDisplayedScTovPbProgressBar() // ProgressBarが表示されていること
.isNotDisplayedScTovInSorry(); // includeが表示されていないこと
registerIdlingResource() // アイドルリソースを登録
.isDisplayedScTovWbTos() // WebViewが表示されていること
.isNotDisplayedScTovPbProgressBar() // ProgressBarが表示されていないこと
.isNotDisplayedScTovInSorry() // includeが表示されていること
;
}
@NonNull
private ScTovPO startScreenTest() {
return new ScManPO()
.clickHamburgerMenu() // ハンバーガーメニュー_押下
.clickTosViewMenuItem(); // 利用規約・免責事項メニューアイテム_押下
}
private ScTovPO registerIdlingResource() {
scenario.onActivity(activity -> {
// TosViewFragment取得
TosViewFragment tosViewFragment =
(TosViewFragment) Objects.requireNonNull(activity
.getSupportFragmentManager()
.findFragmentById(R.id.sc_man_fc_nav_host))
.getChildFragmentManager()
.getFragments()
.get(0);
// アイドルリソース取得
idlingResource = tosViewFragment.getCountingIdlingResource();
// アイドルリソースを登録
IdlingRegistry.getInstance().register(idlingResource);
});
return new ScTovPO();
}
}
👉 今回色々試してみましたが、上記のような形で成功しました。
処理の流れは以下の通りです。
- setUpでアクティビティを起動
- ScreenTest_ScTovでUIテスト開始
- startScreenTestでアクティビティから対象画面への遷移操作を実施
- ScreenTest_ScTovの途中で「アイドルリソースの登録」を行う。
- アイドルリソースをフラグメントから取得し登録する。
- テスト終了後、tearDownでアイドルリソースの登録解除
4,5は#registerIdlingResource
でやっています。アクティビティからフラグメントを取得し、ゲッターを介して取得したものをレジストしています。
テスト評価
Espressoを使ってUIテストを実施します。
Testing started at 10:04 PM ...
04/12 22:04:00: Launching 'ScreenTest_ScTov()' on Pixel 2 API 28 (SmartPhone).
App restart successful without re-installing the following APK(s): androidx.test.orchestrator, androidx.test.services
Running tests
無事テストが成功したことがわかります。
IdlingResourceを使えば、Espressoでの非同期テストも簡単に評価することができました。
めでたしめでたし。