Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

⾃⼰紹介 p ⽒名: 外⼭ 純⽣ (TOYAMA Sumio) @sumio_tym (Twitter) / @sumio (GitHub) p 所属: NTTテクノクロス株式会社 p 業務内容: p 社内Androidプロジェクトの技術⽀援 p 社内Androidプロジェクトのテスト⾃動化⽀援 p プライベート: p STAR(テスト⾃動化研究会) p @IT連載「スマホ向け無料システムテスト⾃動化 ツール」(UI Automator / Appiumの回を担当) 2

Slide 3

Slide 3 text

RxJavaとAndroid p⾯倒なAsyncTaskが不要になる pイベントバスっぽい使い⽅もできる RxJavaに⽤意されている スケジューラーのおかげ! 3

Slide 4

Slide 4 text

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/ よりロゴを引⽤ (※)

Slide 5

Slide 5 text

Espressoの⾮同期処理待ち合わせ 以下の全てが「アイドル状態」になるまで 次の操作に進まないことで実現 pメインスレッド pAsyncTaskのバックグラウンドスレッド RxJavaの⾮同期処理は どちらでもない・・・! 5

Slide 6

Slide 6 text

Espressoの⾮同期処理待ち合わせ AsyncTask以外の⾮同期処理待ち合わせ p 独⾃IdlingResourceを実装すればOK 6 public interface IdlingResource { public String getName(); public boolean isIdleNow(); public void registerIdleTransitionCallback( ResourceCallback callback); } 「今アイドル状態ですか?」 という質問に答えるように実装する

Slide 7

Slide 7 text

お話しすること Espressoテストコードで RxJavaの⾮同期処理の完了を 待ち合わせる⽅法を紹介します! 7

Slide 8

Slide 8 text

使うもの (Espresso) CountingIdlingResourceクラス p increment(): 参照カウントを増やす (⾮同期処理開始時に呼び出す) p decrement(): 参照カウントを減らす (⾮同期処理終了時に呼び出す) ※参照カウントが0のときが「アイドル状態」 8

Slide 9

Slide 9 text

使うもの (RxJava1) RxJavaHooksクラス p setOnScheduleAction(Func1) タスクがスケジューリングされる直前に呼び出される フックを設定する 9 // ログを仕込む例 RxJavaHooks.setOnScheduleAction(oldAction -> { Log.d(TAG, "スケジューリングされた"); return () -> { Log.d(TAG, "実⾏直前"); oldAction.call(); }; });

Slide 10

Slide 10 text

使うもの (RxJava2) RxJavaPluginsクラス p setScheduleHandler(Function super Runnable, ? extends Runnable>) タスクがスケジューリングされる直前に呼び出される フックを設定する 10 // ログを仕込む例 RxJavaPlugins.setScheduleHandler(oldAction ->{ Log.d(TAG, "スケジューリングされた"); return () -> { Log.d(TAG, "実⾏直前"); oldAction.run(); }; });

Slide 11

Slide 11 text

解決⽅針 setOnSchedulerAction / setScheduleHandler (以降スケジューリングフックと呼びます) で、以下の処理を差し込む 1. 実⾏直前にCountingIdlingResourceを インクリメントする 2. 実⾏直後にCountingIldingResourceを デクリメントする 何も実⾏されていないとき ‖ カウント0の「アイドル状態」 11

Slide 12

Slide 12 text

解決コード (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(); }

Slide 13

Slide 13 text

解決コード (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(); }

Slide 14

Slide 14 text

解決コード (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(); }

Slide 15

Slide 15 text

注意: フック設定タイミング RxJavaの⾮同期タスクが無い状態でフッ クを設定する必要がある 15 // Activity起動時に⾮同期処理が⾛る場合は // フック設定後にActivityを起動するようにする @Rule public ActivityTestRule activityRule = new ActivityTestRule<>(MyActivity.class, false, false); @Before public void setUp() { ... // (省略)スケジューラーフックを設定する Espresso.registerIdlingResources(ires); activityRule.launchActivity(null); }

Slide 16

Slide 16 text

限界①: 遅延スケジューリング 実⾏状態以外のタスクがあっても 「アイドル状態」とみなされてしまう 16 // ボタンを押してから3秒間放置するとテキストを表⽰する例 RxView.clicks(button) .debounce(3, TimeUnits.SECONDS) .subscribe(v -> textView.setText("OK")); 3秒待ってる間は 「アイドル状態」 「ボタンを押したらOKと表⽰されること」 というテストは失敗してしまう

Slide 17

Slide 17 text

限界①を回避する 素直に表⽰されるまでポーリングして待つ ※UI AutomatorのAPIを使うのがお⼿軽 17 // ボタンを押す onView(...).perform(click()); // OKと表⽰されるまで待つ uiDevice.wait(Until.hasObject(By.text("OK")), 5000L); ...

Slide 18

Slide 18 text

限界②: フックが既に使われている アプリ側でスケジューリングフックが 設定されている場合 pテストコードで上書きすると アプリが意図しない状態に・・・ 18

Slide 19

Slide 19 text

限界②を回避する 設定済みのフックを更にフックする p テスト実⾏中にアプリ側で再設定されないならOK 19 // RxJava1の例 Func1 oldOnScheduleAction = RxJavaHooks.getOnScheduleAction(); RxJavaHooks.setOnScheduleAction(oldAction -> () -> { try { ires.increment(); oldOnScheduleAction .call(oldAction).call(); } finally { ires.decrement(); } });

Slide 20

Slide 20 text

まとめ Espressoテストコードで RxJavaの⾮同期処理の完了を 待ち合わせる⽅法を紹介しました pスケジューリングフックと CountingIdlingResourceを使う pうまく⾏かない箇所では、諦めて UI AutomatorのAPIを使って待ち合わせる pスケジューリングフックがアプリ側で 設定されている場合は限界あり 20

Slide 21

Slide 21 text

参考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

Slide 22

Slide 22 text

END ご清聴ありがとうございました! 22