🤖
[備忘録] roborazzi導入してみた
案件のアプリケーションにroborazziを導入しようとして、いろいろはまったところがあったので備忘録的な記録をします
ゴール
- junit5を使って単体テストを書いているプロジェクトにroborazziを導入できるようになる
- プレビューテストの妨げになるComposable関数が分かるようになる
依存を追加
// root-level build.gradle.kts
plugin {
// ...
+ id("io.github.takahirom.roborazzi") version "1.29.0" apply false
}
// module-level build.gradle.kts
plugin {
// ...
+ id("io.github.takahirom.roborazzi")
}
android {
// ...
+ testOptions {
+ unitTests {
+ isIncludeAndroidResources = true
+ all {
+ it.systemProperties["robolectric.pixelCopyRenderMode"] = "hardware"
+ }
+ }
+ }
}
// module-level build.gradle.kts
dependencies {
// ...
+ implementation("junit:junit:4.13.2")
+ implementation("org.robolectric:robolectric:4.13")
+ implementation("io.github.takahirom.roborazzi:roborazzi:1.29.0")
+ implementation("io.github.takahirom.roborazzi:roborazzi-junit-rule:1.29.0")
+ implementation("io.github.takahirom.roborazzi:roborazzi-compose:1.29.0")
+ implementation("io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:1.29.0")
+ implementation("o.github.sergio-sastre.ComposablePreviewScanner:android:0.3.2")
}
プレビューテスト自動生成のための記述をする
// module-level build.gradle.kts
roborazzi {
generateComposePreviewRobolectricTests {
enable = true
packages = listOf("your.package.name")
// privateなプレビューを含めたいならincludePrivatePreviewsを利用する
// includePrivatePreviews = true
}
}
スクリーンショットの撮影および差分生成
// 比較元となるブランチに移動
git switch develop
// スクリーンショットを撮影
./gradlew recordRoborazziDemoDebug
// 作業ブランチに移動
git switch {branch}
// 差分を作成
./gradlew compareRoborazziDemoDebug
// tips 差分画像のみを抽出
find . -type f -name "_compare" -exec mv {} . \;
junit-vintage-engine
を追加する
はまりポイント1: junit5を利用している場合は、roborazziによって生成されるテストはJUnit4ベースだったので、JUnit5を使っている場合はjunit-vintage-engineを追加する必要があります
implementation(platform("org.junit:junit-bom:5.11.3"))
// run junit4-based test (roborazzi)
+ testRuntimeOnly("org.junit.jupiter:junit-vintage-engine")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
はまりポイント2: PreviewにDialogを含めないようにする
Dialogを使ったComposable関数について自動生成されたスクリーンショットテストは失敗します
@Composable
fun HogeDialog(
// ...
) {
Dialog(
onDismissRequest = onDismiss,
) {
// content
}
}
java.lang.IllegalStateException: Unable to find the image of the target root component. Does the rendering element exist?
at com.github.takahirom.roborazzi.RoborazziKt.capture(Roborazzi.kt:613)
at com.github.takahirom.roborazzi.ImageCaptureViewAction.perform(Roborazzi.kt:587)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:25)
at androidx.test.espresso.ViewInteraction.-$$Nest$mdoPerform(ViewInteraction.java)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:7)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at android.os.Handler.$$robo$$android_os_Handler$handleCallback(Handler.java:942)
at android.os.Handler.handleCallback(Handler.java)
at android.os.Handler.$$robo$$android_os_Handler$dispatchMessage(Handler.java:99)
...
「レンダリング可能な要素がないんだが?」と怒られてますね
このような場合にはDialog内を切り出して、それのプレビューをテストしてます
Caution: Preview関数名はコンポーネントと同じ名前にしてはいけない
Preview関数名がコンポーネントと同一であればテストが失敗します
@Composable
fun HogeScreen(
// ...
) {
// ...
}
@Preview
@Composable
fun HogeScreen() {
MaterialTheme {
Surface {
HogeScreen(
// ...
)
}
}
}
java.lang.IllegalArgumentException: wrong number of arguments: 2 expected: 5
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkArgumentCount(Unknown Source)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at sergio.sastre.composable.preview.scanner.core.preview.ComposablePreviewInvocationHandler.invoke(ComposablePreviewInvocationHandler.kt:30)
at jdk.proxy4/jdk.proxy4.$Proxy58.invoke(Unknown Source)
at sergio.sastre.composable.preview.scanner.core.preview.ProvideComposablePreview$invoke$1.invoke(ProvideComposablePreview.kt)
at com.github.takahirom.roborazzi.RoborazziPreviewScannerSupportKt$captureRoboImage$1.invoke(RoborazziPreviewScannerSupport.kt:18)
at com.github.takahirom.roborazzi.RoborazziPreviewScannerSupportKt$captureRoboImage$1.invoke(RoborazziPreviewScannerSupport.kt:17)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:441)
...
Tips: メモリは潤沢に用意したほうがよさそう
メモリ不足でテストが失敗しやすいのでヒープサイズを広く確保しておくことをオススメします
android {
// ...
testOptions {
unitTests {
isIncludeAndroidResources = true
all {
maxHeapSize = "4096m"
it.systemProperties["robolectric.pixelCopyRenderMode"] = "hardware"
}
}
}
}
株式会社 カラビナテクノロジーは「命綱や支点を素早く確実に繋ぐカラビナ。そんなカラビナのような役割をテクノロジーで実現したい」という想いのもと、福岡で設立。 主にシステム開発・アプリ開発・ Webサイト制作を行っています。採用情報→karabiner.tech/recruit/requirements/
Discussion