Slide 1

Slide 1 text

品質管理部SWETグループ 田熊 希羽 1

Slide 2

Slide 2 text

● Androidのプロダクトにスクリーンショットを 活用したテストを導入する中で、直面した課題と それらをどのように解決したか ● 導入でつまずかないために検討するべきことと、 気をつけるべきこと 2

Slide 3

Slide 3 text

1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 3

Slide 4

Slide 4 text

● 田熊 希羽(タクマ ノゾミ) ● 品質管理部 SWETグループ所属 ○ Pocochaシステム部兼務 ○ 株式会社Mobility Technologies兼務出向 ● Androidとテストが好き 4

Slide 5

Slide 5 text

2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 5

Slide 6

Slide 6 text

● 手元で画面の表示確認をする ● Visual Regression Test(画像回帰テスト) ● カタログ化してデザインのレビューに利用 6

Slide 7

Slide 7 text

● 手元で画面の表示確認をする ● Visual Regression Test(画像回帰テスト) ● カタログ化してデザインのレビューに利用 共通の目的はUIに関する不具合を早期発見すること 7

Slide 8

Slide 8 text

● 手元で画面の表示確認をする ● Visual Regression Test(画像回帰テスト) ● カタログ化してデザインのレビューに利用 この発表ではこれらをひっくるめて スクリーンショットテストと表現します 8

Slide 9

Slide 9 text

● 画面実装時のデバッグ・動作確認の手段としてス クリーンショットを使用する ● 画面によっては、アプリを操作して遷移したり、 条件によって変わる表示を再現するのが大変 ○ テストで任意の画面や任意の条件を再現したスク リーンショットを取得できれば、動作確認の時間を 削減できる 9

Slide 10

Slide 10 text

● コードの変更前と変更後のスクリーンショット画 像を比較して差分を検知する ● UIに意図しない変更が含まれていないかを確認で きる ● ピクセル単位で比較することで、人の目で見て気 が付きづらいような差分も検知可能 10

Slide 11

Slide 11 text

reg-suitを使った差分レポート 差分がある箇所が赤くなる 今回は文言を修正したこと による差分が検出されている 11

Slide 12

Slide 12 text

● スクリーンショットを一覧化して確認できるよう にする ● 開発者だけでなく、PdM・デザイナー・QAメン バーのレビューに利用できる ● アプリを起動せずにUIを確認できる 12

Slide 13

Slide 13 text

クレジットカードが無効 クレジットカードが期限切れ クレジットカードが有効 13

Slide 14

Slide 14 text

● folio-sec/Fastfile screenshots-preview-generator.rb を参考に しつつカスタマイズ ○ https://github.com/folio-sec/Fastfile/blob/master/ Scripts/screenshots-preview-generator.rb 14

Slide 15

Slide 15 text

1. スクリーンショットテストについて 3. 導入する中で直面した課題 4. 導入でつまずかないために 15

Slide 16

Slide 16 text

● API通信といった外部依存はテスト用データを返 せるように(スタブ化)して、任意の状態再現が簡 単にできるようにする ● 1テストケースのスコープを広げすぎない ○ 例: 任意の状態で画面起動 + スクリーン ショットを撮ってテスト終了 16

Slide 17

Slide 17 text

● API通信といった外部依存はテスト用データを返 せるように(スタブ化)して、任意の状態再現が簡 単にできるようにする ● 1テストケースのスコープを広げすぎない ○ 例: 任意の状態で画面起動 + スクリーン ショットを撮ってテスト終了 ● テスト安定化のため ● テスト実装のハードルを下げるため 17

Slide 18

Slide 18 text

● 実アプリへの忠実度 ● 実行時間 ● 保守コスト ● デバッグコスト テストピラミッド 自動テストのバランス についての指針 18

Slide 19

Slide 19 text

● 実アプリへの忠実度 ● 実行時間 ● 保守コスト ● デバッグコスト この部分に該当 19

Slide 20

Slide 20 text

● Fundamentals of Testing ○ https://developer.android.com/training/testing/fun damentals 20

Slide 21

Slide 21 text

21

Slide 22

Slide 22 text

22 モックライブラリなどで 実装を差し替える

Slide 23

Slide 23 text

@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent = Intent(context, RankingActivity::class.java) val scenario = launchActivity(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } 23

Slide 24

Slide 24 text

@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent = Intent(context, RankingActivity::class.java) val scenario = launchActivity(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } ActivityScenarioといった テスト用の画面起動APIが用意されている 24

Slide 25

Slide 25 text

@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent = Intent(context, RankingActivity::class.java) val scenario = launchActivity(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } 実機やEmulatorを使った Instrumentation Testとして 実行する 25

Slide 26

Slide 26 text

● GO ○ タクシー配車サービス ● 乗務員アプリ ○ GOのタクシー乗務員が利用するアプリ ● Pococha ○ ライブコミュニケーションサービス 26

Slide 27

Slide 27 text

● GO ○ タクシー配車サービス ● 乗務員アプリ ○ GOのタクシー乗務員が利用するアプリ ● Pococha ○ ライブコミュニケーションサービス 実際に運用するのが難しくなり断念 27

Slide 28

Slide 28 text

● GO ○ タクシー配車サービス ● 乗務員アプリ ○ GOのタクシー乗務員が利用するアプリ ● Pococha ○ ライブコミュニケーションサービス うまくいかなかったプロダク トもあわせて、直面した様々 な課題を紹介していきます 28

Slide 29

Slide 29 text

1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 4. 導入でつまずかないために 29

Slide 30

Slide 30 text

テストしたい画面を任意の状 態で起動できるようにする 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる 導入までのハードルを 大きく4つに分割 30

Slide 31

Slide 31 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする 31

Slide 32

Slide 32 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる ● 依存の差し替えをどうするか ● テストの結合範囲をどうするか ● モックライブラリMockkの罠 ● Applicationクラスの障壁 ● 画面起動のパターンに対応する テストしたい画面を任意の状 態で起動できるようにする 32

Slide 33

Slide 33 text

● DIライブラリを導入しているプロダクト ○ ライブラリの仕組みを利用して差し替え ● DIライブラリを導入していないプロダクト ○ 依存先を解決する仕組みを自分で用意 ○ デフォルト引数を駆使しつつ、コンストラク タでインスタンスを差し替え 33

Slide 34

Slide 34 text

● DIライブラリを導入しているプロダクト ○ ライブラリの仕組みを利用して差し替える ● DIライブラリを導入していないプロダクト ○ 依存先を解決する仕組みを自分で用意 ○ デフォルト引数を駆使しつつ、コンストラク タでインスタンスを差し替え ライブラリによって都度調査して対応 今回導入したプロダクトではDagger2・koin 34

Slide 35

Slide 35 text

● DIライブラリを導入しているプロダクト ○ ライブラリの仕組みを利用して差し替える ● DIライブラリを導入していないプロダクト ○ 依存先を解決する仕組みを自分で用意 ○ デフォルト引数を駆使しつつ、コンストラク タでインスタンスを差し替え プロダクトの1つはこれで対応 35

Slide 36

Slide 36 text

● FragmentFactoryと InterceptingActivityFactoryを使えば、テスト 時に起動するFragmentとActivityのインスタン スを差し替えることができる ● 詳細(DIライブラリ未導入の方は是非) ○ Android UIテストでActivityとFragmentにコンストラ クタインジェクションする (Qiita) 36

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

38

Slide 39

Slide 39 text

結合範囲 39

Slide 40

Slide 40 text

GO Pococha 40

Slide 41

Slide 41 text

変更 監視 乗務員アプリの ある画面の例(一部省略) 変更 監視 41

Slide 42

Slide 42 text

変更 監視 全体を結合しないと UIの変更が流れない 変更 監視 42

Slide 43

Slide 43 text

● アプリのアーキテクチャによって結合範囲が変 わってくる ● 結合範囲を広げると、UIが変化する条件を把握す るのが難しくなる ● ユニットテストで担保する範囲を明確にし、スク リーンショットテストのスコープを調整する 43

Slide 44

Slide 44 text

● すでにユニットテストで使用していたmockkの Instrumentation Testサポートを使用 ○ mockk.io/ANDROID ● Android P以上ではinline mock機能により、 finalクラスやobjectのスタブも可能 ○ 44

Slide 45

Slide 45 text

● バイトコードに処理を差し込むことで機能を実現 ○ Mock final and static methods on Android devices ● Androidの実機で動作させるとランダムにクラッ シュする上、クラッシュのログがでない ● Andorid11ではinline mock機能を使わない場合 でも、inline mockのセットアップでクラッシュ 45

Slide 46

Slide 46 text

● inline mockを利用しない ○ サブクラス化してモック生成する機能を利用 ○ 依存がIF化されていない場合、DexOpenerや All-open compiler pluginを導入する必要有 ● Android11でクラッシュする問題は1.10.6で修正 される予定(1.10.0を利用すれば一時的に回避化) 46

Slide 47

Slide 47 text

前提として、Instrumentation Testでは通常のアプ リと同様にApplicationクラスが起動される 47

Slide 48

Slide 48 text

● テストで困るApplicationクラスの実装例 ○ 起動などをトリガーにAPI通信を行い、401エラー だったらログイン画面に遷移させる ○ テストしたい画面の起動をブロックされる テストではテスト用Applicationクラスを使うことで 実行されないようにすることは可能だが... 48

Slide 49

Slide 49 text

● プロダクトコード内で固有のApplcationを直接参 照していると切り離すのが難しい ○ テスト用Applicationクラスに継承してもらう ことで回避は可能 ○ その上で不要な処理を実行しないように改修 ■ Applicationクラスが巨大だと骨が折れる 49

Slide 50

Slide 50 text

● スクリーンショットを取得したい範囲と起動方法 によって変わる ○ Fragment単体 or 親Activity(Fragment)を含むか ● 起動方法のパターン例 ○ PagerAdapterを利用している ○ Navigation componentを利用している 50

Slide 51

Slide 51 text

Fragment Activity Pager 51

Slide 52

Slide 52 text

● 親画面も含んだスクリーンショットを撮る場合 は、親画面から起動する ○ 子画面が参照している外部依存もあわせて差 し替えできるようにする ○ 特にコンストラクタで差し替えをしている場 合は、親画面から渡せるようにする必要あり 52

Slide 53

Slide 53 text

● ActivityScenario・FragamentScenarioを利用す ると起動しているActivity・Fragmentのインス タンスにアクセスできる ● そこからNavigationControllerを取得し、 navigationを実行することで画面操作をせずに 遷移することが可能 53

Slide 54

Slide 54 text

val activityScenario = launchActivity(intent) activityScenario.onActivity { act : MyActivity -> val navController = Navigation.findNavController(act, R.id.nav) navController.navigate(..) } 起動したActivityのインスタンスからpublicアクセス できるものはテストコードからもアクセス可能 54

Slide 55

Slide 55 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる ● 依存の差し替えをどうするか ● テストの結合範囲をどうするか ● モックライブラリMockkの罠 ● Applicationクラスの障壁 ● 画面起動のパターンに対応する テストしたい画面を任意の状 態で起動できるようにする 55

Slide 56

Slide 56 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する テストしたい画面を任意の状 態で起動できるようにする スクリーンショットをとって みたら、実アプリと違う表示 になってしまうことがある 56

Slide 57

Slide 57 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● 非同期処理の待ち合わせ ● スクリーンショットAPIの罠 ● FragmentScenarioの罠 ● スクロールする画面に対応する テストしたい画面を任意の状 態で起動できるようにする 57

Slide 58

Slide 58 text

非同期処理は? テストで結合する範囲に バックグラウンドスレッ ドの起動がある? Dispatcherを差し替 えできるようにする 準備なしでOK 58

Slide 59

Slide 59 text

非同期処理は? テストで結合する範囲に バックグラウンドスレッ ドの起動がある? 準備なしでOK Repositoryでスレッド起動 かつ、Repositoryがスタブ 化されているケース 59

Slide 60

Slide 60 text

● Dispatcherの差し替え実装例 ○ DroidKaigi/conference-app-2019 ■ CoroutinePlugin.kt ● DataBinding利用時 ○ android/architecture-samples ■ DataBindingIdlingResource.kt 60

Slide 61

Slide 61 text

● FragmentScencarioで内部的使われるActivity は、AppCompatActivityを ● FragmentScenaioで起動した画面は、 AppCompatでのみ認識されるView属性が正常に 表示されない 61

Slide 62

Slide 62 text

いない... app:srcCompatで 指定した VectorDrawable 62

Slide 63

Slide 63 text

● FragmentScenarioの使用を避ける ○ 親のActivityから起動する ○ 空のAppCompatActivityにattachして Fragmentを起動できる仕組みを用意する ■ 内部的にActivityScenarioを使った代替の FragmentScenarioなど 63

Slide 64

Slide 64 text

● Androidのテストで利用できるスクリーンショッ トのAPIは複数ある ● APIによって、実際のアプリと見た目が異なる スクリーンショットがとれる場合がある ● 詳細 ○ Androidのテストで利用できるスクリーンショット取得API のまとめ (Qiita) 64

Slide 65

Slide 65 text

○ ✕ リフレクション を使用すれば可 ✕ ○ ○ リフレクション を使用すれば可 SurfaceView 単体のみ可 ✕ (画面全体のみ) ○ ○ ○ 65

Slide 66

Slide 66 text

● スクロールをしながらスクリーンショットを取得 ○ スクリーンショット → スクロール → スク リーンショット... ● 全体をスクリーンショットできるように画面をリ サイズしたあとスクリーンショットを撮る 66

Slide 67

Slide 67 text

自動でスクロール + スクリーンショット 取得を行うEspressoの Actionを用意 67

Slide 68

Slide 68 text

コンテンツのサイズに あわせてViewをリサイズ するオプションを追加 ただし UiDevice#takeSceenshot は端末で見える領域しか キャプチャしないため併用 できない 端末上でみえる 範囲 68

Slide 69

Slide 69 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● 非同期処理の待ち合わせ ● スクリーンショットAPIの罠 ● FragmentScenarioの罠 ● スクロールする画面に対応する テストしたい画面を任意の状 態で起動できるようにする 69

Slide 70

Slide 70 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する テストしたい画面を任意の状 態で起動できるようにする Visual Regression Testで 差分の誤検知をしないように 70

Slide 71

Slide 71 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● ステータスバーを固定化する ● 地図の表示を固定化する ● 時刻の表示を固定化する テストしたい画面を任意の状 態で起動できるようにする 71

Slide 72

Slide 72 text

● 時刻や電池残量など表示が動的に変わる ● 全画面のスクリーンショットを撮る際には含まれ てしまう 72

Slide 73

Slide 73 text

● 開発者オプションのシステムUIデモモード ○ Demo Mode for the Android System UI ● テストコードから有効化するには(値は↑を参照) ○ UiAutomation#executeShellCommandで adbを実行し、デモモードを有効化 ○ 必要なコマンドをBroadcastで発行 73

Slide 74

Slide 74 text

時刻を10:00・電池残量を100に固定 74

Slide 75

Slide 75 text

● 読み込み状態によって差分がでる ● 中身が常に更新されうる 75

Slide 76

Slide 76 text

● Google Mapsの場合は、道路や地名の表示等をオ プションで非表示にできる ○ https://mapstyle.withgoogle.com/ ● 一番安定するのは何も表示しない ● カスタムの設定を作成し、テストコードから画面 を起動した際にデフォルトの設定を上書きする 76

Slide 77

Slide 77 text

地図の中身はすべて非表示 カスタムで実装している マーカーのみになった 77

Slide 78

Slide 78 text

● AndroidのUI widgetであるTextClockは、内部的 にCalendarのインスタンスを保持しており、 外から時刻を差し替えるのが難しい ● 時刻フォーマットを(HH:mm等)指定するメソッド があるので、そこに時刻のフォーマットとして解 釈できない文字列を入れることで固定化可能 78

Slide 79

Slide 79 text

activityScenario.onActivity { act -> act.findViewById(R.id.clock).format24Hour = "10:00" } findViewByIdで内部のViewにアクセスして状態を変更する 直接INVISIBLEにするといった力技も可能 79

Slide 80

Slide 80 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● ステータスバーを固定化する ● 地図の表示を固定化する ● 時刻の表示を固定化する テストしたい画面を任意の状 態で起動できるようにする 80

Slide 81

Slide 81 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする 81

Slide 82

Slide 82 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる ● スクリーンショットテストが 流行らない テストしたい画面を任意の状 態で起動できるようにする 82

Slide 83

Slide 83 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする ● スクリーンショットテストが 流行らない 現在力を入れて取組中 83

Slide 84

Slide 84 text

● 自動テストにありがちな、テストを書く時間がな くて後回しになるという問題はスクリーンショッ トテストでも共通 ● 効果が高いのは実装と一緒にテストを書くこと ○ 少しでもテスト実装のハードルを下げられるような 取り組みを実施中 84

Slide 85

Slide 85 text

● 画面起動やUI操作のヘルパーを実装してボイラー プレートを削減 ● ペアプロやモブプロでスクリーンショットテスト の実装をサポート ● スクリーンショット画像を端末から取得する Gradleタスクを用意 85 etc...

Slide 86

Slide 86 text

1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 86

Slide 87

Slide 87 text

テストしたい画面を任意の状 態で起動できるようにする 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる 4つのポイントでつまづき そうな箇所がないかを確認 87

Slide 88

Slide 88 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる ● 依存の差し替えができるか ● アーキテクチャにあわせて結合 範囲を検討する ● モックライブラリ利用の可否 ● Applicationクラスにテストの 邪魔になりそうな処理はないか ● アプリの画面構成とどのような 起動経路があるかを確認する テストしたい画面を任意の状 態で起動できるようにする 88

Slide 89

Slide 89 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● 利用している非同期処理の機構 にあわせて待ち合わせの仕組み を実装する ● 画面の特性にあわせて、スク リーンショットAPIを選択する ● FragmentScenarioの代替手段 を用意する ● スクロールする画面の対応方針 を決めて仕組みを実装する テストしたい画面を任意の状 態で起動できるようにする 89

Slide 90

Slide 90 text

スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する ● 画面の中で動的に変わる箇所と 固定化する方法を突き止める テストしたい画面を任意の状 態で起動できるようにする 90

Slide 91

Slide 91 text

意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる ● スクリーンショットテストの メリットについてチームで合意 ● 実装のハードルは都度改善 テストしたい画面を任意の状 態で起動できるようにする 91

Slide 92

Slide 92 text

● UIテスタビリティを意識しつつ開発する ○ 画面実装時にも、テストで邪魔な処理が 差し替えできるようになっているかを意識 ● スクリーンショットテストでカバーしすぎようと しない ○ ユニットテストで見るべきでは?を考える 92

Slide 93

Slide 93 text

1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 93

Slide 94

Slide 94 text

● スクリーンショットはUIに関する不具合の早期発 見に活用できる ● Androidプロダクトに導入するには4つのポイン トでハードルがある ● プロダクトの特性にあわせて、上記のポイントで ハマりどころがないか確認する 94

Slide 95

Slide 95 text

95