RxJavaの非同期処理に負けないEspressoテストコードを書く / Let's Write Espresso Tests Synchronizing with RxJava's Asynchronous Processing

RxJavaの非同期処理に負けないEspressoテストコードを書く / Let's Write Espresso Tests Synchronizing with RxJava's Asynchronous Processing

Rx Ja Night Vol.2
で発表予定の「RxJavaの非同期処理に負けないEspressoテストコードを書く」の発表資料です。

サンプルコードは
https://github.com/sumio/RxJavaEspressoSample
で公開しています。

17997dee8a3da090f62d8cf8c494d8ff?s=128

TOYAMA Sumio

June 12, 2017
Tweet

Transcript

  1. RxJavaの⾮同期処理に負けない Espressoテストコードを書く 2017.06.12 外⼭ 純⽣ (sumio) 1 Rx Ja Night

    Vol.2
  2. ⾃⼰紹介 p ⽒名: 外⼭ 純⽣ (TOYAMA Sumio) @sumio_tym (Twitter) /

    @sumio (GitHub) p 所属: NTTテクノクロス株式会社 p 業務内容: p 社内Androidプロジェクトの技術⽀援 p 社内Androidプロジェクトのテスト⾃動化⽀援 p プライベート: p STAR(テスト⾃動化研究会) p @IT連載「スマホ向け無料システムテスト⾃動化 ツール」(UI Automator / Appiumの回を担当) 2
  3. RxJavaとAndroid p⾯倒なAsyncTaskが不要になる pイベントバスっぽい使い⽅もできる RxJavaに⽤意されている スケジューラーのおかげ! 3

  4. Espresso p Android公式のUIテストフレームワーク p URL phttps://goo.gl/x8eP5C phttps://goo.gl/lmrlJI p アプリ側の⾮同期処理の完了を ⾃動的に待ち合わせてくれる(例外あり)

    4 (※) https://google.github.io/android-testing-support-library/docs/espresso/cheatsheet/ よりロゴを引⽤ (※)
  5. Espressoの⾮同期処理待ち合わせ 以下の全てが「アイドル状態」になるまで 次の操作に進まないことで実現 pメインスレッド pAsyncTaskのバックグラウンドスレッド RxJavaの⾮同期処理は どちらでもない・・・! 5

  6. Espressoの⾮同期処理待ち合わせ AsyncTask以外の⾮同期処理待ち合わせ p 独⾃IdlingResourceを実装すればOK 6 public interface IdlingResource { public

    String getName(); public boolean isIdleNow(); public void registerIdleTransitionCallback( ResourceCallback callback); } 「今アイドル状態ですか?」 という質問に答えるように実装する
  7. お話しすること Espressoテストコードで RxJavaの⾮同期処理の完了を 待ち合わせる⽅法を紹介します! 7

  8. 使うもの (Espresso) CountingIdlingResourceクラス p increment(): 参照カウントを増やす (⾮同期処理開始時に呼び出す) p decrement(): 参照カウントを減らす

    (⾮同期処理終了時に呼び出す) ※参照カウントが0のときが「アイドル状態」 8
  9. 使うもの (RxJava1) RxJavaHooksクラス p setOnScheduleAction(Func1<Action0, Action0>) タスクがスケジューリングされる直前に呼び出される フックを設定する 9 //

    ログを仕込む例 RxJavaHooks.setOnScheduleAction(oldAction -> { Log.d(TAG, "スケジューリングされた"); return () -> { Log.d(TAG, "実⾏直前"); oldAction.call(); }; });
  10. 使うもの (RxJava2) RxJavaPluginsクラス p setScheduleHandler(Function<? super Runnable, ? extends Runnable>)

    タスクがスケジューリングされる直前に呼び出される フックを設定する 10 // ログを仕込む例 RxJavaPlugins.setScheduleHandler(oldAction ->{ Log.d(TAG, "スケジューリングされた"); return () -> { Log.d(TAG, "実⾏直前"); oldAction.run(); }; });
  11. 解決⽅針 setOnSchedulerAction / setScheduleHandler (以降スケジューリングフックと呼びます) で、以下の処理を差し込む 1. 実⾏直前にCountingIdlingResourceを インクリメントする 2.

    実⾏直後にCountingIldingResourceを デクリメントする 何も実⾏されていないとき ‖ カウント0の「アイドル状態」 11
  12. 解決コード (RxJava1) 12 private CountingIdlingResource ires; @Before public void setUp()

    { ires = new CountingIdlingResource("RxJava"); RxJavaHooks .setOnScheduleAction(oldAction -> () -> { try { ires.increment(); oldAction.call(); } finally { ires.decrement(); }}); Espresso.registerIdlingResources(ires); } @After public void tearDown() { Espresso.unregisterIdlingResources(ires); RxJavaHooks.reset(); }
  13. 解決コード (RxJava2) 13 private CountingIdlingResource ires; @Before public void setUp()

    { ires = new CountingIdlingResource("RxJava"); RxJavaPlugins .setScheduleHandler(oldAction -> () -> { try { ires.increment(); oldAction.run(); } finally { ires.decrement(); }}); Espresso.registerIdlingResources(ires); } @After public void tearDown() { Espresso.unregisterIdlingResources(ires); RxJavaHooks.reset(); }
  14. 解決コード (RxJava2) 14 private CountingIdlingResource ires; @Before public void setUp()

    { ires = new CountingIdlingResource("RxJava"); RxJavaPlugins .setScheduleHandler(oldAction -> () -> { try { ires.increment(); oldAction.run(); } finally { ires.decrement(); }}); Espresso.registerIdlingResources(ires); } @After public void tearDown() { Espresso.unregisterIdlingResources(ires); RxJavaHooks.reset(); }
  15. 注意: フック設定タイミング RxJavaの⾮同期タスクが無い状態でフッ クを設定する必要がある 15 // Activity起動時に⾮同期処理が⾛る場合は // フック設定後にActivityを起動するようにする @Rule

    public ActivityTestRule<MyActivity> activityRule = new ActivityTestRule<>(MyActivity.class, false, false); @Before public void setUp() { ... // (省略)スケジューラーフックを設定する Espresso.registerIdlingResources(ires); activityRule.launchActivity(null); }
  16. 限界①: 遅延スケジューリング 実⾏状態以外のタスクがあっても 「アイドル状態」とみなされてしまう 16 // ボタンを押してから3秒間放置するとテキストを表⽰する例 RxView.clicks(button) .debounce(3, TimeUnits.SECONDS)

    .subscribe(v -> textView.setText("OK")); 3秒待ってる間は 「アイドル状態」 「ボタンを押したらOKと表⽰されること」 というテストは失敗してしまう
  17. 限界①を回避する 素直に表⽰されるまでポーリングして待つ ※UI AutomatorのAPIを使うのがお⼿軽 17 // ボタンを押す onView(...).perform(click()); // OKと表⽰されるまで待つ

    uiDevice.wait(Until.hasObject(By.text("OK")), 5000L); ...
  18. 限界②: フックが既に使われている アプリ側でスケジューリングフックが 設定されている場合 pテストコードで上書きすると アプリが意図しない状態に・・・ 18

  19. 限界②を回避する 設定済みのフックを更にフックする p テスト実⾏中にアプリ側で再設定されないならOK 19 // RxJava1の例 Func1<Action0, Action0> oldOnScheduleAction

    = RxJavaHooks.getOnScheduleAction(); RxJavaHooks.setOnScheduleAction(oldAction -> () -> { try { ires.increment(); oldOnScheduleAction .call(oldAction).call(); } finally { ires.decrement(); } });
  20. まとめ Espressoテストコードで RxJavaの⾮同期処理の完了を 待ち合わせる⽅法を紹介しました pスケジューリングフックと CountingIdlingResourceを使う pうまく⾏かない箇所では、諦めて UI AutomatorのAPIを使って待ち合わせる pスケジューリングフックがアプリ側で

    設定されている場合は限界あり 20
  21. 参考URL ① RxAndroidのIssue #149 (様々な解決⽅法が議論されている) https://github.com/ReactiveX/RxAndroid/issues/149 ② Iñaki Villar「Espresso: Beyond

    the Basics」(Mobilization 2016) (本トークとほぼ同じ⽅法を紹介) https://goo.gl/AiQ2uJ ③ Joshua Kovach「Retrofitting Espresso」 (RxのスケジューラをAsyncTaskに差し替える ⽅法を紹介) https://goo.gl/2p3E6S 21
  22. END ご清聴ありがとうございました! 22