Open1

【Google I/O 2023】Scalable UI testing solutions

watabeewatabee

Scalable UI testing solutions

https://www.youtube.com/watch?v=L6CSaH0kDnI&list=PLOU2XLYxmsIIwZQkAPhJZg8jaNrrHk1DH&index=120

Espresso Device API (0:59~)

端末の回転や折りたたみ端末の開閉の操作などで Configuration Change が発生する。

新しい Espresso Device API を使って Configuration Change のテストができる。
テストには API レベル24以降の Android の仮想デバイスを使う。
また Configuration Change のテストは同期的な方法となるので、スリープ関数などを使う必要がない。

設定について

Android Gradle Plugin 8.2 の最新の Canary バージョンを使う。

plugins {
    id 'com.android.application' version '8.2.0-alpha04' apply false
}

そして Android emulator は 33.1.10 以降をインストールする。

androidTest のマニフェストに以下のパーミッションが必要。

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

最後に app モジュールの Gradle ファイルに以下を設定。

testOptions {
    emulatorAccess {
        enabled true
    }
}
...

dependencies {
    androidTestImplementation 'androidx.test.espresso:espresso-device:1.0.0-alpha05'
}

画面回転のテストは以下のような感じで使う。

import androidx.test.espresso.device.action.ScreenOrientation
import androidx.test.espresso.device.rules.ScreenOrientationRule
...
@get:Rule
val screenOrientationRule: ScreenOrientationRule = 
    ScreenOrientationRule(ScreenOrientation.PORTRAIT)
    
@Test
fun myRotationTest() {
    ...
    // Sets the device to landscape orientation during test execution
    onDevice().setScreenOrientation(ScreenOrientation.LANDSCAPE)
    // Checks that the appropriate UI is displayed in this orientation
    composeTestRule.onNodeWithTag("NavRail").assertIsDisplayed()
    composeTestRule.onNodeWithTag("BottomBar").assertDoesNotExist()
}

折りたたみ端末の開閉状態の変更のテストは以下のような感じ。

// Test starts in folded state and check for a compact mode layout
@Test
fun myUnfoledTest() {
    // Starts the test with the device in a folded state.
    onDevice().setClosedMode()
    composeTestRule.onNodeWithTag("BottomBar").assertIsDisplayed()
    composeTestRule.onNodeWithTag("NavRail").assertDoesNotExist()
    ...
    // Unfolds the device into a flat posture.
    onDevice().setFlatMode()
    composeTestRule.onNodeWithTag("NavRail").assertIsDisplayed()
    composeTestRule.onNodeWithTag("BottomBar").assertDoesNotExist()
}

折りたたみ端末じゃない場合にテストをスキップすることもできる。

@Test
// Test skips on devices that don't support a FLAT posture.
@RequiresDeviceMode(mode = FLAT)
fun myUnfoldedTest() {
    ...
}

上記のルールは各テストケースとテストクラス全体に追加することができる。

Gradle Managed Devices (5:38~)

  • 昨年公開されたローカル、CI ともに使える仮想デバイス
  • build.gradle に少し設定を書くと、Android Gradle Plugin が端末のダウンロード&作成、スナップショットとテストキャッシュの管理、デバイスのシャットダウンを行なってくれる
  • Gradle ManagedDevice では、Firebase test Lab で選択した実機もしくは仮想デバイスを利用して CI からテスト実行することができるようになった

以下のように設定する。

// In your Project Gradle file
plugins {
    ...
    id 'com.google.firebase.testlab' version '0.0.1-alpha02' apply false 
}

// In your gradle.properties file
android.experimental.testOptions.managedDevices.customDevice=true
plugins {
    ...
    id 'com.google.firebase.testlab'
}

...
    
firebaseTestLab {
    managedDevices {
        create("GalaxyS22Ultra") {
            device = "b0q" // Device model_id
            apiLevel = 33
        }
    }
}

Firebase Test Lab でテストを実行したい場合は Firebase アカウントの認証が必要になるので、以下のように設定できる。

firebaseTestLab {
    serviceAccountCredentials = file('~/my-service-account.json')
}

実行するコマンドは以下のような感じ。

$ gradlew GalaxyS22UltraDebugAndroidTest

テストの結果は HTML レポート、protofile(Android Studio で開ける)、Firebase コンソールで確認できる。

複数の端末で確認したい場合は、以下のように設定する。

firebaseTestLab {
    managedDevices {
        create("GalaxyS22Ultra") { ... }
        create("GalaxyZFlip3") { ... }
        create("GalaxyZFold3") { ... }
        create("GalaxyTabS3") { ... }
    }
}

一つのタスクでこれらの端末のテストを実行したい場合、デバイスグループを追加する。

android {
    ...
    testOptions {
        managedDevices {
            groups {
                samsungGalaxy {
                    targetDevices.add(devices.GalaxyS22Ultra)
                    targetDevices.add(devices.GalaxyZFlip3)
                    targetDevices.add(devices.GalaxyZFold3)
                    targetDevices.add(devices.GalaxyTabS3)
                    ...
                }
            }
        }
    }
}

テストの実行は以下のコマンドで行える。

$ gradlew samsungGalaxyGroupDebugAndroidTest

Screenshot testing (9:10~)

Android Gradle Plugin 8.2 から実験的機能として、レイアウトのプレビューから画像を作成し、スクリーンショットテストに使えるようになる。

まず最初に app モジュール内に新しいディレクトリかソースセットを作成し、名前を screenshotTest にする。

~/MyProject/app/src/screenshotTest/

このソースセット内で Kotlin のファイルを作成し、プレビューの設定を行う。

// Set up a preview as you normally would inside the new source set
@Preview(device = Devices.PHONE)
@Composable
fun ChatPreview() {
    ChatTheme {
        ConversationContent(
            uiState = exampleUiState,
            navigateToProfile = {  }
        )
    }
}

以下のコマンドを実行する。

$ gradlew debugScreenshotTest --record-reference-images

Gradle は reference image を作成し、プロジェクトに保持する。この生成された画像をコミットすれば CI のテストに使える。

もし誰かが再利用可能なコンポーネントを変更した場合、予期しない結果となるはず。
もしこの変更が対象の画面に影響を与える場合、スクリーンショットテストはリグレッションを検知する。
そのために、以下のテストタスクを実行する。

$ gradlew debugScreenshotTest

テスト実行後、Gradle は HTML レポートを生成し、その中に reference image とテストで実行した際の画像が失敗したテストごとに含まれる。
結果には diff も提供され、変更した箇所がハイライトされる。

リグレッションのテストは多くの異なるレイアウト、コンポーネント、画面サイズやテーマに対して行える。
まだ実験的な機能なので、プレビューのドキュメントのチェックが必要。