Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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
で公開しています。

TOYAMA Sumio

June 12, 2017
Tweet

More Decks by TOYAMA Sumio

Other Decks in Programming

Transcript

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    カウント0の「アイドル状態」
    11

    View Slide

  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();
    }

    View Slide

  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();
    }

    View Slide

  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();
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide