はじめに
こんにちは、こんばんは、佐藤佑哉ことさとゆーです。今回はreg-suitとroborazziを使用したVRTを実装してみようと思います。
動機
最近のインターンでxmlファイルからJetpack Composeへの移行をした際に、Jetpack Composeに入れ替える時、変更前と変更後の画面を目視で比較する必要があるため、レビューなどで漏れてしまうことがありました。これを機に、機械的に分析してヒューマンエラーをなくすための、VRTを触ってみたいと思うようになりました。
使用技術の紹介
この記事で紹介するVRTツールの技術はreg-suitとroborazziです。
roborazziとは
roborazziとは、Android端末を使わずにJVM上でスクリーンショットを撮影することができるライブラリです。スクリーンショットテストライブラリとして開発されており、テスト中に画像のキャプチャを取得し、事前に取得しておいた期待する画面のキャプチャと比較することで検証を行うことができます。
reg-suitを用いたのは、CA.flutterなどでも紹介されていた、
reg-suitとは
reg-suitとは、VRTのためのCLIを提供するCLIであり、現在の画像と以前の画像を比較し、HTMLに差異を検出してくれるため、差分を視覚的に表示することができます。
使用理由
- reg-suit:
- CA.flutterの勉強会でこの技術を知り、本番のプロダクトでも使用されるくらいの信頼性と安定性がある
- roborazzi
- nowinandroidや他の企業でも採用されており、非常に人気が高い
- JVM上で動くため、Androidの環境に依存しないテストの実現が可能
実装フロー
今回は、VRTの実装の観点とreg-suitの技術の都合上、以下のように実装していきます。
-
roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
-
reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziを使用して、画面をスクリーンショット
roborazziの依存関係を追加
roborazziは、下記README
に記載されているものを参考に依存関係を追加しました。
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziを使用してスクリーンショットを撮る
次に、roborazziを使用してスクリーンショットを撮ってみましょう。
スクリーンショットができるように、gradle.properties
で次のプロパティを有効にします。
roborazzi.test.record=true
早速、スクリーショットができるようになったので、簡単なComposableをスクリーンショットしてみましょう。(ソースコードはこちら)
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel7)
class SampleTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
@Test
fun sampleTest() {
composeTestRule.setContent { MaterialTheme { Text("Hello, World!") } }
composeTestRule.onRoot().captureRoboImage("src/test/screenshots/Sample/sample_test.png")
}
}
このコードのみで簡単にComposableのスクリーンショットできるのがわかりました。(実際のスクリーンショットはこちら)
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziを使用して、VRTを行う
次に、roborazziを使用して、VRTを行っていきます。
任意の画面をセット
任意のComposableをテストにセットし、変更前の画面のスクリーンショットを取得します。
@RunWith(AndroidJUnit4::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(sdk = [33], qualifiers = RobolectricDeviceQualifiers.Pixel7)
class HomeScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
@get:Rule //後に説明
val roborazziRule = DefaultRoborazziRule
@Test
fun homeContent_success() {
val uiState = HomeUiState.Success(
uncompletedTodos = emptyList(),
completedTodos = emptyList(),
categories = listOf(myTaskCategory),
selectedCategoryId = 1
)
val snackBarHostState = SnackbarHostState()
composeTestRule.setContent {
MaterialTheme {
HomeContent(
uiState = uiState,
/*...*/
)
}
}
composeTestRule.onRoot().captureRoboImage()
}
}
次にComposableのレウアウトを変更します。
今回は、適当に空白を入れておきます。
Spacer(Modifier.height(8.dp))
コマンドの入力
以下のコマンドを入力すると、変更前と変更後の画面のスクリーンショットを比較してくれます。
https://github.com/takahirom/roborazzi?tab=readme-ov-file#build-setup
.gradlew compareRoborazziDebug
入力後に次のディレクトリroot_project/module_name/build/outputs/roborazzi/src/*
を見てみましょう。すると、スクリーンショットが3枚入っています。
name.png |
name_actual.png |
---|---|
name_compare.png |
---|
変更前と変更後、差分のスクリーンショットを提供してくれているのがわかります。
また、ファイルroot_project/module_name/build/reports/roborazzi/index.html
をみると、以下のローカルサイトを見ることができます。(一部のレイアウトを抜粋)
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
reg-suitを使用してVRTを行う
reg-suitの導入
reg-suitを本プロジェクトで導入するために次のコマンドを入力します。
$ reg-suit init
初期化が終わると、reg-suitの設定が始まります。
? Working directory of reg-suit. .reg # reg-suitのワーキングディレクトリのディレクトリ名を決定
? Append ".reg" entry to your .gitignore file. Yes
? Directory contains actual images. directory_contains_actual_images //変更後の画面のスクリーンショットのディレクトリ先
? Threshold, ranges from 0 to 1. Smaller value makes the comparison more sensitive. 0 ## デフォルトは0
[reg-suit] info Set up reg-notify-github-plugin:
? notify-github plugin requires a client ID of reg-suit GitHub app. Open installation window in your browser Yes ## reg-notify-github-pluginを使用するために、リポジトリのClientIDが必要
? This repositoriys client ID of reg-suit GitHub app #RepositoryのClientIDを記入
上記のreg-suitの設定を完了すると、以下のファイルが出来上がってると思います。
root_project/
├ package.json
├ package-lock.json
├ regconfig.json
├ ...
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziのスクリーンショットの出力先を変更する
reg-suitでは、スクリーンショットの機能はないため、roborazziを使用して補完します。
## RoborazziRule.Options.outputDirectoryPathでアウトプット先を変更することができる。
roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory
## ファイル名をクラス名.メソッド名.pngと統一することができる
roborazzi.record.namingStrategy=testClassAndMethod
RoborazziRuleを使用して、スクリーンショットの出力先をreg-suit導入時に入れたディレクトリ先を記入します。
val DefaultRoborazziRule =
RoborazziRule(RoborazziRule.Options(outputDirectoryPath = "../../actual_images"))
@get:Rule
val roborazziRule = DefaultRoborazziRule
現在は、Featureモジュール上でVRTを行っているためこのようにしておりますが、他のモジュール(designsytemmモジュールなど)階層が違うモジュールのVRTを行う際には、VRT専用のモジュールでも作ると、よりこのRoborazziRuleの汎用性が高まりそうだなと記事を書きながら気づきました。
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
手動で変更前のスクリーンショットを入れておく
reg-suitでは、変更前のスクリーンショットを.reg/expectedに追加されている必要があるため、手動でスクリーンショットを入れておきます。
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziで変更後のスクリーンショットを撮り、VRTを行う
roborazziから変更後のスクリーンショットを撮っておきます。
./gradlew recordRoborazziDebug # ./actual_imagesにスクリーンショットが出力される
次に以下のコマンドを入力し、reg-suitによるVRTを行います。
reg-suit run
これをすると、以下のディレクトリが自動生成されます
.reg/
├ actual/ # 変更後のスクリーンショットが出力される
├ diff/ # 差分
├ expeted/ # 手動でスクリーンショットを入れたディレクトリ
├ index.html # 差分レポート
├ ...
index.html
はこちらになっております。reg-suitは機能満載で、差分がとてもみやすいサイトになっていました。
これで、roborazziとreg-suitを使用したVRTを実現することはできました🎉
- roborazziを使用して、先ほどのスクリーンショットとVRTを行う
- roborazziの導入
- スクリーンショットを撮る
- VRTを行う
- reg-suitを使用してroborazziで取得したスクリーンショットを使用して、VRTを行う
- reg-suitの導入
- roborazziのスクリーンショットの出力先を変更する
- 手動で変更前のスクリーンショットを入れておく
- roborazziで変更後のスクリーンショットを撮り、VRTを行う
まとめ
本記事では、reg-suitとroborazziを使用したVRTを実装いたしました。
個人的に実装コストやAndroidアプリ開発における汎用性を考えると、roborazziがかなり使い勝手が良さそうです。インターフェースの細かな調整のレビューが重視される場合は、reg-suitを使用してみるのもありだと感じました!
誰かの開発の助けになれば幸いです🙇♂️