N予備校 Android チームでテックリードをしている鎌田です。
N予備校 Android チームでは、スクリーンショットテストを導入しました。
本記事では、どのようにスクリーンショットテストを導入したか、背景と手順をまとめます。
スクリーンショットテストの概要については、他のスクリーンショットテストに関する記事でも説明されているため、参考 URL を参照してください。
背景
PR を作成する際、UI に変更がある場合は修正前後の画面キャプチャを貼り付けるようにしていました。
ですが、都度画面キャプチャを自前で用意しなければいけないのが面倒なのと、ぱっと見ではわかりづらいデザイン変更の場合だと画面キャプチャからでもわかりづらいことがありました。
そこで、DroidKaigi 2023 アプリのように PR 上で差分のある UI に対してのみスクリーンショットの差分を提示することで、UI に変更がないことを証明でき、かつ画面キャプチャが不要になり手間が減るのではと考えました。
参考:Adding tests to BookmarkScreenTest by hiesiea · Pull Request #932 · DroidKaigi/conference-app-2023
前提
Jetpack Compose を使用していること。
Jetpack Compose を使用していない UI については今回の方法ではできませんのでご承知おきください。
また、本アプリではマルチモジュールを採用しております。
モジュール構成図は以下の通りです。
環境
Roborazzi、Showkase、GitHub Actions を使って構築しました。
Roborazzi を採用した理由としては、類似ライブラリである Paparazzi と比較して他社での採用事例が多く、スクロールできるなどの独自機能もあるためです。
Showkase を採用した理由としては、Preview 関数で生成した Composable をスクリーンショットテストにそのまま使用でき、コード削減できると考えたためです。
GitHub Actions を採用した理由としては、Roborazzi を実行するのに GitHub Actions の方がサンプルコードもあり実装しやすそうだったためです。
また、極力 DroidKaigi 2023 アプリのような形の実行形式を目指していたためです。
実装
ビルド周りの設定
まず、ルートの build.gradle.kts
に対して以下の設定を追加します。
plugins {
...
id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
id("io.github.takahirom.roborazzi") version "1.10.1" apply false
}
次に、app
モジュールの build.gradle.kts
に対して以下の設定を追加します。
本アプリでは、上述の通り Showkase を利用してスクリーンショットテストを実装したかったため、app
モジュールにのみスクリーンショットテストを作成する方針で進めました。
plugins {
...
id("io.github.takahirom.roborazzi")
}
...
dependencies {
...
testImplementation("io.github.takahirom.roborazzi:roborazzi:1.10.1")
testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.10.1")
}
次に、Composable を実装している各モジュール(モジュール構成図で言うところの UI モジュール)の build.gradle.kts
に対して以下の設定を追加します。
plugins {
...
id("com.google.devtools.ksp")
}
...
dependencies {
...
debugImplementation("com.airbnb.android:showkase:1.0.2")
implementation("com.airbnb.android:showkase-annotation:1.0.2")
kspDebug("com.airbnb.android:showkase-processor:1.0.2")
}
// private な Preview 関数は無視するようにし、対象を絞れるようにする
ksp {
arg("skipPrivatePreviews", "true")
}
本アプリでは、すぐに見た目で確認できるよう、基本的に実装した Composable に対して必ず Preview 関数を作成しています。
ですが、全ての Preview 関数を対象としようとすると OutOfMemoryError が発生してテストに失敗してしまうので、対象を絞る必要がありました。
そのため、private な Preview 関数は無視するようにし、対象を絞れるようにしました。
// private が付いていると Showkase 側で無視するようになるのでスクリーンショットテストの対象にならない
@Preview
@Composable
private fun SamplePreview() {
Sample(
...
)
}
// private を外すことで Showkase が認識できるようになるのでスクリーンショットテストの対象になる
@Preview
@Composable
fun SampleForScreenshotTestPreview() {
Sample(
...
)
}
スクリーンショットテストのコードを追加
まず、Preview 関数で生成した Composable を参照できるよう、ShowkaseRootModule
インタフェースの実装を app
モジュールに定義します。
@ShowkaseRoot
class ShowkaseRootModule : ShowkaseRootModule
次に、スクリーンショットテストを行う PreviewTest
を実装します。
@RunWith(ParameterizedRobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(
qualifiers = RobolectricDeviceQualifiers.Pixel7Pro,
)
class PreviewTest(
private val showkaseBrowserComponent: ShowkaseBrowserComponent,
) {
@Test
fun previewScreenshot() {
val filePath =
DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH + "/" + showkaseBrowserComponent.componentKey + ".png"
captureRoboImage(
filePath,
) {
showkaseBrowserComponent.component()
}
}
companion object {
@ParameterizedRobolectricTestRunner.Parameters
@JvmStatic
fun components(): Iterable<Array<Any?>> {
return Showkase.getMetadata().componentList.map { showkaseBrowserComponent ->
arrayOf(showkaseBrowserComponent)
}
}
}
}
できる限り画面サイズの大きいデバイスで確認したいと思い、@Config
アノテーションでデバイスは Pixel7Pro を指定しています。
Showkase.getMetadata().componentList
に Showkase で抽出した Preview 関数群の情報が格納されています。
それを previewScreenshot
の中で showkaseBrowserComponent
として受け取り、使用しています。
次に、Android Studio 上で PreviewTest
を実行する場合、gradle.properties
に以下の設定を追加します。
roborazzi.test.record=true
GitHub のワークフローを追加
以下のサンプルコードをほぼ流用した形で実装しました。
-
CompareScreenshot.yml
- 比較したスクリーンショットを記録するためのワークフロー
-
CompareScreenshotComment.yml
- CompareScreenshot.yml の結果を該当 PR にコメントとして出力するためのワークフロー
-
StoreScreenshot.yml
- スクリーンショットを記録するためのワークフロー
ただし、上記のサンプルコードはデフォルトブランチへ向けた PR のみを想定しており、それ以外だとうまく動作しないので注意してください。
また、GitHub Actions のリソースは有限であり、プランによっては他リポジトリとリソースを共有しているケースもあります。
その場合、スクリーンショットテストでリソースを食い潰さないよう、他チームとの調整が必要です。
実装結果
実行結果は以下の通りです。
例えばパディングを調整したときに、以下のように赤く差分が表示されるようになりました。
また、Compose 関連のライブラリがアップデートされたときなど、直接 UI の変更を行なっていない場合にも変更があったとわかるようになりました。
まとめ
本記事では、N予備校 Android チームでスクリーンショットテストを導入したので、背景と手順をまとめました。
スクリーンショットテストを導入したことで、PR 上で差分のある UI に対してのみスクリーンショットの差分が提示されるようになり、UI に変更があったかどうかがわかるようになりました。
特に、Compose 関連のライブラリがアップデートされたときなど、直接 UI の変更を行なっていない場合にも変更があったとわかるようになったのが大きかったです。
まだ一部の画面しか対象にしていないのと、画面キャプチャを用意する・しないについての基準が決まっていないなどあるため、そこは今後検討していきたいです。