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

cookpad.apk#4

Kyohei Kato
February 13, 2020

 cookpad.apk#4

Kyohei Kato

February 13, 2020
Tweet

More Decks by Kyohei Kato

Other Decks in Programming

Transcript

  1. Shared Test を実装する上で
    気をつけなければいけないこと
    Cookpad.apk #4

    View full-size slide

  2. 自己紹介
    ● 加藤 恭平(@ksfee684)
    ● モバイル基盤部所属
    ● Android アプリプロジェクトの開発効率化、基盤技術開発

    View full-size slide

  3. アジェンダ
    ● Android におけるテスト
    ● Robolectric はどのように機能するか
    ● Robolectric でできること/できないこと
    ● テスト実行環境の選択
    ● Shared Test とは
    ● Shared Test を実装する上で気をつけること

    View full-size slide

  4. 今日伝えたいこと
    DroidKaigi で sumio_tym さんの
    「Robolectric の限界を理解して UI テストを高速に実行しよう」をぜひ御覧ください

    View full-size slide

  5. Android におけるテスト

    View full-size slide

  6. https://developer.android.com/training/testing/fundamentals#testing-pyramid

    View full-size slide

  7. テスト実行環境
    ● 実機端末 or エミュレータ
    ○ Instrumentation Test
    ○ テストをパッケージングして端末上で実行
    ○ 実行時間が長い
    ○ 複雑でメンテナンスコストが高く、デバッグが難しい
    ● ローカル JVM
    ○ クラス単位での Unit テスト
    ○ Android API を利用するテストがほぼ実行不可能
    ○ 実行時間が短い
    ○ シンプルかつデバッグも比較的容易

    View full-size slide

  8. なぜローカル JVM で Android API を利用できないのか
    ● クラス単体では OS の機能をベースとした動作を確認できない
    ○ Handler, Looper, UI(View レンダリング)
    ● ローカル JVM では以下のようなものがうまく動かない
    ○ イベントループ
    ○ ライフサイクルイベント
    ● ローカル JVM でもテストを実行したい

    View full-size slide

  9. そこで Robolectric
    ● Android API を利用するテストをローカル JVM で動作させるすごいやつ
    ○ なぜかエミュレータを使わなくてもテストが動作する
    ○ なぜかローカル JVM で動作する
    ○ なぜか普通の JUnit テストより遅い

    View full-size slide

  10. Robolectric は
    どのように機能するか

    View full-size slide

  11. Android の OS の挙動を模倣している

    View full-size slide

  12. 動かない部分はごまかす(Shadow)
    ● 実際には実現できない部分、普通のローカル JVM でテストを実行する上で工夫しなければ
    行けない部分を Shadow として実装
    ● ほとんどのオブジェクトは Android API(SDK)の実装をベースとして一部のメソッドだけ置き
    換えている

    View full-size slide

  13. ローカル JVM で Android API を利用できるようになったので
    Activity や Fragment のテストもすべて Robolectric で完璧!!

    View full-size slide

  14. すべてのテストを Robolectric で書けば良い?
    ● そんな夢みたいな話はない
    ● OS の挙動をあくまで模倣しているだけなので実際の動きとは異なるものになっている
    ● では具体的にはどういった点が違うのか

    View full-size slide

  15. Robolectric で
    できること/できないこと

    View full-size slide

  16. できること
    ● 結構いろいろできる
    ○ ライフサイクルメソッドの呼び出し
    ■ ActivityScenario, FragmentScenario
    ■ onCreate, onActivityResult
    ○ Context およびリソースへのアクセス
    ○ Espresso を用いた UI テスト
    ■ View へのアクセス

    View full-size slide

  17. http://robolectric.org/androidx_test/
    // GIVEN
    val contactName = "Test User"
    val scenario = launchActivity()
    // WHEN
    // Enter contact name
    onView(withId(R.id.contact_name_text)).perform(typeText(contactName))
    // Destroy and recreate Activity
    scenario.recreate()
    // THEN
    // Check contact name was preserved.
    onView(withId(R.id.contact_name_text)).check(matches(withText(contactName)))

    View full-size slide

  18. できないこと
    ● Shadow で実装されたメソッドで大きく Android 実行環境と異なる箇所がいくつか存在する
    ○ Looper のイベントループ
    ○ Activity 及び ActivityManager の状態管理
    ○ View レンダリング

    View full-size slide

  19. Looper
    ● 元々 Looper にポストされたタスクを同期的に実行する実装だった
    ● v4.3 からは PausedLooper という仕組みが入り、 Queue に一時的にタスクをため、任意のタイミング
    で execute が可能となった
    ● 参考
    ○ http://robolectric.org/blog/2019/06/04/paused-looper/
    ○ https://github.com/robolectric/robolectric/blob/master/robolectric/src/test/java/org/robolectric/shadows/ShadowLega
    cyLooperTest.java
    ○ https://github.com/robolectric/robolectric/blob/master/robolectric/src/test/java/org/robolectric/shadows/ShadowPaus
    edLooperTest.java

    View full-size slide

  20. http://robolectric.org/blog/2019/06/04/paused-looper/
    List events = new ArrayList<>();
    events.add("before");
    new Handler(Looper.getMainLooper()).post(() -> events.add("after"));
    events.add("between");
    assertThat(events).containsExactly("before", "between", "after").inOrder();

    View full-size slide

  21. http://robolectric.org/blog/2019/06/04/paused-looper/
    List events = new ArrayList<>();
    events.add("before");
    new Handler(Looper.getMainLooper()).post(() -> events.add("after"));
    events.add("between");
    // the 'after' task is posted, but has not been executed yet
    assertThat(events).containsExactly("before", "between").inOrder();
    // execute all tasks posted to main looper
    shadowOf(getMainLooper()).idle();
    assertThat(events).containsExactly("before", "between", "after").inOrder();

    View full-size slide

  22. 詳しくは…
    より Robolectric の詳しい内容は sumio_tym さんの
    「Robolectric の限界を理解して UI テストを高速に実行しよう」をおすすめします

    View full-size slide

  23. テスト実行環境の選択

    View full-size slide

  24. Android 実行環境でのテスト実行がボトルネックに
    ● Android 実行環境構築のコスト
    ○ CI 環境の構築
    ● 実行のコスト
    ○ ビルド -> パッケージング -> インストール -> 実行
    ● とはいえ Robolectric で置き換えが可能か判断しにくいものもある

    View full-size slide

  25. Shared Test とは

    View full-size slide

  26. テスト実行タイミングによって実行環境を変える
    ● Robolectric と Android 実行環境どちらでも動作するようにテストを実装する
    ○ 実行頻度の高いタイミングでは Robolectric での実行結果で判断する
    ○ クリティカルなタイミングでは Android 実行環境化でテストを実行することでテスト結
    果を判断する
    ● 日々の開発におけるテストの実行速度を高速化する
    ○ どちらの実行環境でも実行されるテスト -> Shared Test

    View full-size slide

  27. Shared Test を実装する上で気をつけること
    ● Robolectric が理解できていないうちはすべて Android 実行環境上で動作させるべき
    ○ 下手にバグを踏まない
    ○ 実装をする上で考慮しなければならない事項を減らす
    ● UI テストの実行コストを減らしたい
    ○ テストごとに何を検証しているかを考える
    ○ ライフサイクル、非同期タスクの処理の順序等を考慮しなければならない場合はさける
    ● 実際のサンプルとかは?
    ○ CodeLab から消えている…

    View full-size slide

  28. クックパッドでは?
    ● 現在のところクックパッドアプリでは Shared Test はありません!
    ○ 一部リソース(TestApplication 等)を共有するだけに留まっている
    ○ 今後テスト実行コストを評価した際に検討するかも
    …?

    View full-size slide

  29. まとめ
    ● Android アプリにおける Robolectric の働き
    ● タイミングによって実行環境が変わる Shared Test
    ● Shared Test の実装目安

    View full-size slide