こんにちは、グノシー Android アプリの開発担当の Liang です。
この記事は Gunosy Advent Calendar 2023 の23日目の記事です。前回の記事は Naoto Koizumi さんの「CircleCIからGithub Actionsに大引っ越しした話」でした。
今回は CircleCI での自動化テスト:Android Instrumentation Test において、スクリーンショットを確認出来る仕組みを紹介したいと思います。
背景
グノシー Android アプリでは、CircleCI を用いた自動 UI テストを導入しています。テスト失敗の原因をログだけで判断していたので、十分な情報が足りないため、対応に時間がかかっていました。そこでテスト失敗時のスクリーンショットを見れるようにして、確認・修正の効率を向上させようと考えました。
前提対応:CI/CD での自動化テスト
スクリーンショットを撮る UI テストを導入するにあたり、エミュレーターを立ち上げられる Virtual Machine が必要になります。下記を参考に、Github Actions 等の CI / CD ツールで Android System Image を導入してください。 circleci.com
テストケースの実行結果を検知
グノシー Android アプリの運用では、テストケースが失敗の時だけ、スクリーンショットを撮るので、junit のTestWatcher
を使って、各ケースのRule
として失敗のコールバックを検知します。
- FailedTestWatcher
class FailedTestWatcher : TestWatcher() { override fun succeeded(description: Description?) { super.succeeded(description) } override fun failed(e: Throwable?, description: Description?) { super.failed(e, description) // take a screenshot } }
- Test Class
@Rule val ruleChain = RuleChain .emptyRuleChain() .around(FailedTestWatcher())
スクリーンショットを取得
テストが失敗したタイミングFailedTestWatcher.failed
で、まずuiAutomation.takeScreenshot
を通じて今のスクリーンショットであるBitmap
を取得します。 実行したエミュレーターのストレージに画像を書き込むため、ContentResolver.insert
のUri
をMediaStore.VOLUME_EXTERNAL_PRIMARY
に指定して、書き込む用のパスUri
を取得します。
書き込む内容ContentValues
には下記のカラムを記入し、外部から画像をダウンロード出来るようにします
Media.DISPLAY_NAME
画像ファイル名{TestClass}_{TestMethod}
となるように指定しています。
Media.MIME_TYPE: image/png
ファイルタイプMediaColumns.RELATIVE_PATH
ディレクトリ名Environment.DIRECTORY_PICTURES
メディアコンテンツとして保存します。
最後にcontentResolver.openOutputStream
を経由し、Bitmap
を PNG フォマットのInputStream
として、画像ファイルに転換します。
val bitmap = InstrumentationRegistry.getInstrumentation().uiAutomation.takeScreenshot() val contentResolver = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "${description?.testClass?.simpleName}_${description?.methodName}") put(MediaStore.Images.Media.MIME_TYPE, "image/png") put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) } val uri = contentResolver.insert(contentUri, contentValues) ?: return contentResolver.openOutputStream(uri)?.use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
CI/CD の artifacts に保存
CircleCI では、ARTIFACTS
を通じてスクリーンショットである画像ファイルを確認するため、該当Job
には保存用adb
コマンドの Bash を実行させます。adb shell find
を経由し、エミュレーターのstorage/emulated/0/Pictures
に保存した PNG 画像ファイルのパスを探し出します。 次にadb shell cat
から、指定したフォルダーに各画像をコピーして保存します。
mkdir -p failed_screenshots screenshots = $(adb shell find storage/emulated/0/Pictures -name "*.png") for screenshot in screenshots; do \ adb shell cat "${screenshot}" > "failed_screenshots/${screenshot##*/}"; \ done
これで CircleCI Dashbord のARTIFACTS
上でテスト失敗のスクリーンショットを確認出来るようになりました。
感想
Android 開発においての UI Test は、異なる端末環境での実行結果も変わる可能性が大いにあり、レアケースを含めた不具合を解消するには、やはりテスト過程の確認が必要不可欠です。動画としてレコードしたい場合もTestWatcher
とadb shell screenrecord
を通じで、実現が可能でしょう。
明日は Hironori Yamamoto さんの「大規模データ基盤における dbt のオーケストレーション」です。お楽しみに!