Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Android スクリーンショットテスト 3つのプロダクトに導入する中で倒してきた課題 / A...
Search
tkmnzm
March 03, 2021
Programming
2
1.2k
Android スクリーンショットテスト 3つのプロダクトに導入する中で倒してきた課題 / Android Screenshot Test Problems solved by introducing into 3 products
DeNA TechCon 2021での発表資料です
https://techcon.dena.com/2021/session/16/
tkmnzm
March 03, 2021
Tweet
Share
More Decks by tkmnzm
See All by tkmnzm
AndroidアプリのUIバリエーションをあの手この手で確認する / Check UI variations of Android apps by various means
tkmnzm
1
790
Androidアプリの良いユニットテストを考える / Thinking about good unit tests for Android apps
tkmnzm
5
7.6k
Google I:O 2023 Androidの自動テストアップデートまとめ / Google I:O 2023 Android Testing Update Recap
tkmnzm
0
570
コルーチンのエラーをテストするためのTips / Tips for testing Kotlin Coroutine errors
tkmnzm
0
910
Androidのモダンな技術選択にあわせて自動テストも アップデートしよう / Update your automated tests to match Android's modern technology choices
tkmnzm
3
2.1k
SWET dev-vitalチームによるプロジェクトの健康状態可視化の取り組み / SWET dev-vital team's efforts to visualize the health of the project
tkmnzm
1
1.2k
モバイルアプリテスト入門 / Getting Started with Mobile App Testing
tkmnzm
1
490
25分で作るAndroid Lint / Android Lint made in 25 minutes
tkmnzm
0
850
2年半ぶりのプロダクト開発であらためて感じた自動テストの大切さ / realized the importance of automatic testing with product development for the first time in two and a half years
tkmnzm
1
760
Other Decks in Programming
See All in Programming
Macとオーディオ再生 2024/11/02
yusukeito
0
370
シェーダーで魅せるMapLibreの動的ラスタータイル
satoshi7190
1
480
詳細解説! ArrayListの仕組みと実装
yujisoftware
0
580
Nurturing OpenJDK distribution: Eclipse Temurin Success History and plan
ivargrimstad
0
880
EventSourcingの理想と現実
wenas
6
2.3k
3 Effective Rules for Using Signals in Angular
manfredsteyer
PRO
0
100
Make Impossible States Impossibleを 意識してReactのPropsを設計しよう
ikumatadokoro
0
170
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
聞き手から登壇者へ: RubyKaigi2024 LTでの初挑戦が 教えてくれた、可能性の星
mikik0
1
130
macOS でできる リアルタイム動画像処理
biacco42
9
2.4k
受け取る人から提供する人になるということ
little_rubyist
0
230
Snowflake x dbtで作るセキュアでアジャイルなデータ基盤
tsoshiro
2
520
Featured
See All Featured
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
42
9.2k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
The Cost Of JavaScript in 2023
addyosmani
45
6.7k
Docker and Python
trallard
40
3.1k
Why You Should Never Use an ORM
jnunemaker
PRO
54
9.1k
Visualization
eitanlees
145
15k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
28
2k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
38
1.8k
The Invisible Side of Design
smashingmag
298
50k
Testing 201, or: Great Expectations
jmmastey
38
7.1k
Being A Developer After 40
akosma
86
590k
Transcript
品質管理部SWETグループ 田熊 希羽 1
• Androidのプロダクトにスクリーンショットを 活用したテストを導入する中で、直面した課題と それらをどのように解決したか • 導入でつまずかないために検討するべきことと、 気をつけるべきこと 2
1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 3
• 田熊 希羽(タクマ ノゾミ) • 品質管理部 SWETグループ所属 ◦ Pocochaシステム部兼務 ◦
株式会社Mobility Technologies兼務出向 • Androidとテストが好き 4
2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 5
• 手元で画面の表示確認をする • Visual Regression Test(画像回帰テスト) • カタログ化してデザインのレビューに利用 6
• 手元で画面の表示確認をする • Visual Regression Test(画像回帰テスト) • カタログ化してデザインのレビューに利用 共通の目的はUIに関する不具合を早期発見すること 7
• 手元で画面の表示確認をする • Visual Regression Test(画像回帰テスト) • カタログ化してデザインのレビューに利用 この発表ではこれらをひっくるめて スクリーンショットテストと表現します
8
• 画面実装時のデバッグ・動作確認の手段としてス クリーンショットを使用する • 画面によっては、アプリを操作して遷移したり、 条件によって変わる表示を再現するのが大変 ◦ テストで任意の画面や任意の条件を再現した スクリーンショットを取得できれば、動作確 認の時間を削減できる
9
• コードの変更前と変更後のスクリーンショット画 像を比較して差分を検知する • UIに意図しない変更が含まれていないかを確認で きる • ピクセル単位で比較することで、人の目で見て気 が付きづらいような差分も検知可能 10
reg-suitを使った差分レポート 差分がある箇所が赤くなる 今回は文言を修正したこと による差分が検出されている 11
• スクリーンショットを一覧化して確認できるよう にする • 開発者だけでなく、PdM・デザイナー・QAメン バーのレビューに利用できる • アプリを起動せずにUIを確認できる 12
クレジットカードが無効 クレジットカードが期限切れ クレジットカードが有効 13
• folio-sec/Fastfile screenshots-preview-generator.rb を参考に しつつカスタマイズ ◦ https://github.com/folio-sec/Fastfile/blob/master/ Scripts/screenshots-preview-generator.rb 14
1. スクリーンショットテストについて 3. 導入する中で直面した課題 4. 導入でつまずかないために 15
• API通信といった外部依存はテスト用データを返 せるように(スタブ化)して、任意の状態再現が簡 単にできるようにする • 1テストケースのスコープを広げすぎない ◦ 例: 任意の状態で画面起動 +
スクリーン ショットを撮ってテスト終了 16
• API通信といった外部依存はテスト用データを返 せるように(スタブ化)して、任意の状態再現が簡 単にできるようにする • 1テストケースのスコープを広げすぎない ◦ 例: 任意の状態で画面起動 +
スクリーン ショットを撮ってテスト終了 • テスト安定化のため • テスト実装のハードルを下げるため 17
• 実アプリへの忠実度 • 実行時間 • 保守コスト • デバッグコスト テストピラミッド 自動テストのバランス
についての指針 18
• 実アプリへの忠実度 • 実行時間 • 保守コスト • デバッグコスト この部分に該当 19
• Fundamentals of Testing ◦ https://developer.android.com/training/testing/fun damentals 20
21
22 モックライブラリなどで 実装を差し替える
@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent
= Intent(context, RankingActivity::class.java) val scenario = launchActivity<RankingActivity>(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } 23
@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent
= Intent(context, RankingActivity::class.java) val scenario = launchActivity<RankingActivity>(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } ActivityScenarioといった テスト用の画面起動APIが用意されている 24
@Test fun capture() { // 画面起動前にテストデータのセットアップを行う // テストしたい画面の起動 val intent
= Intent(context, RankingActivity::class.java) val scenario = launchActivity<RankingActivity>(intent ) scenario.onActivity activity // スクリーンショットを取得・保存 } } 実機やEmulatorを使った Instrumentation Testとして 実行する 25
• GO ◦ タクシー配車サービス • 乗務員アプリ ◦ GOのタクシー乗務員が利用するアプリ • Pococha
◦ ライブコミュニケーションサービス 26
• GO ◦ タクシー配車サービス • 乗務員アプリ ◦ GOのタクシー乗務員が利用するアプリ • Pococha
◦ ライブコミュニケーションサービス 実際に運用するのが難しくなり断念 27
• GO ◦ タクシー配車サービス • 乗務員アプリ ◦ GOのタクシー乗務員が利用するアプリ • Pococha
◦ ライブコミュニケーションサービス うまくいかなかったプロダク トもあわせて、直面した様々 な課題を紹介していきます 28
1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 4. 導入でつまずかないために 29
テストしたい画面を任意の状 態で起動できるようにする 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる 導入までのハードルを 大きく4つに分割
30
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする 31
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる • 依存の差し替えをどうするか • テストの結合範囲をどうするか
• モックライブラリMockkの罠 • Applicationクラスの障壁 • 画面起動のパターンに対応する テストしたい画面を任意の状 態で起動できるようにする 32
• DIライブラリを導入しているプロダクト ◦ ライブラリの仕組みを利用して差し替える • DIライブラリを導入していないプロダクト ◦ 依存先を解決する仕組みを自分で用意 ◦ デフォルト引数を駆使しつつ、コンストラク
タでインスタンスを差し替え 33
• DIライブラリを導入しているプロダクト ◦ ライブラリの仕組みを利用して差し替える • DIライブラリを導入していないプロダクト ◦ 依存先を解決する仕組みを自分で用意 ◦ デフォルト引数を駆使しつつ、コンストラク
タでインスタンスを差し替え ライブラリによって都度調査して対応 今回導入したプロダクトではDagger2・koin 34
• DIライブラリを導入しているプロダクト ◦ ライブラリの仕組みを利用して差し替える • DIライブラリを導入していないプロダクト ◦ 依存先を解決する仕組みを自分で用意 ◦ デフォルト引数を駆使しつつ、コンストラク
タでインスタンスを差し替え プロダクトの1つはこれで対応 35
• FragmentFactoryと InterceptingActivityFactoryを使えば、テスト 時に起動するFragmentとActivityのインスタン スを差し替えることができる • 詳細(DIライブラリ未導入の方は是非) ◦ Android UIテストでActivityとFragmentにコンストラ
クタインジェクションする (Qiita) 36
37
38
結合範囲 39
GO Pococha 40
変更 監視 乗務員アプリの ある画面の例(一部省略) 変更 監視 41
変更 監視 全体を結合しないと UIの変更が流れない 変更 監視 42
• アプリのアーキテクチャによって結合範囲が変 わってくる • 結合範囲を広げると、UIが変化する条件を把握す るのが難しくなる • ユニットテストで担保する範囲を明確にし、スク リーンショットテストのスコープを調整する 43
• すでにユニットテストで使用していたmockkの Instrumentation Testサポートを使用 ◦ mockk.io/ANDROID • Android P以上ではinline mock機能により、
finalクラスやobjectのスタブも可能 ◦ 44
• バイトコードに処理を差し込むことで機能を実現 ◦ Mock final and static methods on Android
devices • Androidの実機で動作させるとランダムにクラッ シュする上、クラッシュのログがでない • Andorid11ではinline mock機能を使わない場合 でも、inline mockのセットアップでクラッシュ 45
• inline mockを利用しない ◦ サブクラス化してモック生成する機能を利用 ◦ 依存がIF化されていない場合、DexOpenerや All-open compiler pluginを導入する必要有
• Android11でクラッシュする問題は1.10.6で修正 される予定(1.10.0を利用すれば一時的に回避化) 46
前提として、Instrumentation Testでは通常のアプ リと同様にApplicationクラスが起動される 47
• テストで困るApplicationクラスの実装例 ◦ 起動などをトリガーにAPI通信を行い、401エ ラーだったらログイン画面に遷移させる ◦ テストしたい画面の起動をブロックされる テストではテスト用Applicationクラスを使うことで 実行されないようにすることは可能だが... 48
• プロダクトコード内で固有のApplcationを直接参 照していると切り離すのが難しい ◦ テスト用Applicationクラスに継承してもらう ことで回避は可能 ◦ その上で不要な処理を実行しないように改修 ▪ Applicationクラスが巨大だと骨が折れる
49
• スクリーンショットを取得したい範囲と起動方法 によって変わる ◦ Fragment単体 or 親Activity(Fragment)を含むか • 起動方法のパターン例 ◦
PagerAdapterを利用している ◦ Navigation componentを利用している 50
Fragment Activity Pager 51
• 親画面も含んだスクリーンショットを撮る場合 は、親画面から起動する ◦ 子画面が参照している外部依存もあわせて差 し替えできるようにする ◦ 特にコンストラクタで差し替えをしている場 合は、親画面から渡せるようにする必要あり 52
• ActivityScenario・FragamentScenarioを利用す ると起動しているActivity・Fragmentのインス タンスにアクセスできる • そこからNavigationControllerを取得し、 navigationを実行することで画面操作をせずに 遷移することが可能 53
val activityScenario = launchActivity<MyActivity>(intent) activityScenario.onActivity { act : MyActivity ->
val navController = Navigation.findNavController(act, R.id.nav) navController.navigate(..) } 起動したActivityのインスタンスからpublicアクセス できるものはテストコードからもアクセス可能 54
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる • 依存の差し替えをどうするか • テストの結合範囲をどうするか
• モックライブラリMockkの罠 • Applicationクラスの障壁 • 画面起動のパターンに対応する テストしたい画面を任意の状 態で起動できるようにする 55
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する テストしたい画面を任意の状 態で起動できるようにする スクリーンショットをとって みたら、実アプリと違う表示
になってしまうことがある 56
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • 非同期処理の待ち合わせ • スクリーンショットAPIの罠
• FragmentScenarioの罠 • スクロールする画面に対応する テストしたい画面を任意の状 態で起動できるようにする 57
非同期処理は? テストで結合する範囲に バックグラウンドスレッ ドの起動がある? Dispatcherを差し替 えできるようにする 準備なしでOK 58
非同期処理は? テストで結合する範囲に バックグラウンドスレッ ドの起動がある? 準備なしでOK Repositoryでスレッド起動 かつ、Repositoryがスタブ 化されているケース 59
• Dispatcherの差し替え実装例 ◦ DroidKaigi/conference-app-2019 ▪ CoroutinePlugin.kt • DataBinding利用時 ◦ android/architecture-samples
▪ DataBindingIdlingResource.kt 60
• FragmentScencarioで内部的使われるActivity は、AppCompatActivityを • FragmentScenaioで起動した画面は、 AppCompatでのみ認識されるView属性が正常に 表示されない 61
いない... app:srcCompatで 指定した VectorDrawable 62
• FragmentScenarioの使用を避ける ◦ 親のActivityから起動する ◦ 空のAppCompatActivityにattachして Fragmentを起動できる仕組みを用意する ▪ 内部的にActivityScenarioを使った代替の FragmentScenarioなど
63
• Androidのテストで利用できるスクリーンショッ トのAPIは複数ある • APIによって、実際のアプリと見た目が異なる スクリーンショットがとれる場合がある • 詳細 ◦ Androidのテストで利用できるスクリーンショット取得API
のまとめ (Qiita) 64
◦ ✕ リフレクション を使用すれば可 ✕ ◦ ◦ リフレクション を使用すれば可 SurfaceView
単体のみ可 ✕ (画面全体のみ) ◦ ◦ ◦ 65
• スクロールをしながらスクリーンショットを取得 ◦ スクリーンショット → スクロール → スク リーンショット... •
全体をスクリーンショットできるように画面をリ サイズしたあとスクリーンショットを撮る 66
自動でスクロール + スクリーンショット 取得を行うEspressoの Actionを用意 67
コンテンツのサイズに あわせてViewをリサイズ するオプションを追加 ただし UiDevice#takeSceenshot は端末で見える領域しか キャプチャしないため併用 できない 端末上でみえる 範囲
68
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • 非同期処理の待ち合わせ • スクリーンショットAPIの罠
• FragmentScenarioの罠 • スクロールする画面に対応する テストしたい画面を任意の状 態で起動できるようにする 69
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する テストしたい画面を任意の状 態で起動できるようにする Visual Regression
Testで 差分の誤検知をしないように 70
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • ステータスバーを固定化する • 地図の表示を固定化する
• 時刻の表示を固定化する テストしたい画面を任意の状 態で起動できるようにする 71
• 時刻や電池残量など表示が動的に変わる • 全画面のスクリーンショットを撮る際には含まれ てしまう 72
• 開発者オプションのシステムUIデモモード ◦ Demo Mode for the Android System UI
• テストコードから有効化するには(値は↑を参照) ◦ UiAutomation#executeShellCommandで adbを実行し、デモモードを有効化 ◦ 必要なコマンドをBroadcastで発行 73
時刻を10:00・電池残量を100に固定 74
• 読み込み状態によって差分がでる • 中身が常に更新されうる 75
• Google Mapsの場合は、道路や地名の表示等をオ プションで非表示にできる ◦ https://mapstyle.withgoogle.com/ • 一番安定するのは何も表示しない • カスタムの設定を作成し、テストコードから画面
を起動した際にデフォルトの設定を上書きする 76
地図の中身はすべて非表示 カスタムで実装している マーカーのみになった 77
• AndroidのUI widgetであるTextClockは、内部的 にCalendarのインスタンスを保持しており、 外から時刻を差し替えるのが難しい • 時刻フォーマットを(HH:mm等)指定するメソッド があるので、そこに時刻のフォーマットとして解 釈できない文字列を入れることで固定化可能 78
activityScenario.onActivity { act -> act.findViewById<TextClock>(R.id.clock).format24Hour = "10:00" } findViewByIdで内部のViewにアクセスして状態を変更する 直接INVISIBLEにするといった力技も可能
79
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • ステータスバーを固定化する • 地図の表示を固定化する
• 時刻の表示を固定化する テストしたい画面を任意の状 態で起動できるようにする 80
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする 81
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる • スクリーンショットテストが 流行らない テストしたい画面を任意の状
態で起動できるようにする 82
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる テストしたい画面を任意の状 態で起動できるようにする • スクリーンショットテストが
流行らない 現在力を入れて取組中 83
• 自動テストにありがちな、テストを書く時間がな くて後回しになるという問題はスクリーンショッ トテストでも共通 • 効果が高いのは実装と一緒にテストを書くこと ◦ 少しでもテスト実装のハードルを下げられる ような取り組みを実施中 84
• 画面起動やUI操作のヘルパーを実装してボイラー プレートを削減 • ペアプロやモブプロでスクリーンショットテスト の実装をサポート • スクリーンショット画像を端末から取得する Gradleタスクを用意 85
etc...
1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 86
テストしたい画面を任意の状 態で起動できるようにする 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる 4つのポイントでつまづき そうな箇所がないかを確認
87
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる • 依存の差し替えができるか • アーキテクチャにあわせて結合
範囲を検討する • モックライブラリ利用の可否 • Applicationクラスにテストの 邪魔になりそうな処理はないか • アプリの画面構成とどのような 起動経路があるかを確認する テストしたい画面を任意の状 態で起動できるようにする 88
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • 利用している非同期処理の機構 にあわせて待ち合わせの仕組み を実装する
• 画面の特性にあわせて、スク リーンショットAPIを選択する • FragmentScenarioの代替手段 を用意する • スクロールする画面の対応方針 を決めて仕組みを実装する テストしたい画面を任意の状 態で起動できるようにする 89
スクリーンショットテストを 定着させる 意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する • 画面の中で動的に変わる箇所と 固定化する方法を突き止める テストしたい画面を任意の状
態で起動できるようにする 90
意図した通りのスクリーン ショットを取得する 安定したスクリーンショット を取得する スクリーンショットテストを 定着させる • スクリーンショットテストの メリットについてチームで合意 •
実装のハードルは都度改善 テストしたい画面を任意の状 態で起動できるようにする 91
• UIテスタビリティを意識しつつ開発する ◦ 画面実装時にも、テストで邪魔な処理が 差し替えできるようになっているかを意識 • スクリーンショットテストでカバーしすぎようと しない ◦ ユニットテストで見るべきでは?を考える
92
1. スクリーンショットテストについて 2. 3つのAndroidプロダクトへの導入 3. 導入する中で直面した課題 4. 導入でつまずかないために 93
• スクリーンショットはUIに関する不具合の早期発 見に活用できる • Androidプロダクトに導入するには4つのポイン トでハードルがある • プロダクトの特性にあわせて、上記のポイントで ハマりどころがないか確認する 94
95