Häufige Muster

Sie können Ihre Compose-App mit bewährten Ansätzen und Mustern testen.

Isoliert testen

Mit ComposeTestRule können Sie eine Aktivität starten, in der eine beliebige zusammensetzbare Funktion angezeigt wird: Ihre gesamte Anwendung, ein einzelner Bildschirm oder ein kleines Element. Außerdem ist es ratsam, zu prüfen, ob Ihre Composables richtig gekapselt sind und unabhängig voneinander funktionieren. So lassen sich UI-Tests einfacher und gezielter durchführen.

Das bedeutet nicht, dass Sie nur UI-Unittests erstellen sollten. UI-Tests, die größere Teile Ihrer Benutzeroberfläche abdecken, sind ebenfalls sehr wichtig.

Nachdem Sie eigene Inhalte festgelegt haben, können Sie auf die Aktivität und die Ressourcen zugreifen.

Häufig müssen Sie den zu testenden Inhalt mit composeTestRule.setContent festlegen und auch auf Aktivitätsressourcen zugreifen, um beispielsweise zu prüfen, ob ein angezeigter Text mit einer String-Ressource übereinstimmt. Sie können setContent jedoch nicht für eine Regel aufrufen, die mit createAndroidComposeRule() erstellt wurde, wenn die Aktivität sie bereits aufruft.

Ein gängiges Muster hierfür ist das Erstellen eines AndroidComposeTestRule mit einer leeren Aktivität wie ComponentActivity.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun myTest() {
        // Start the app
        composeTestRule.setContent {
            MyAppTheme {
                MainScreen(uiState = exampleUiState, /*...*/)
            }
        }
        val continueLabel = composeTestRule.activity.getString(R.string.next)
        composeTestRule.onNodeWithText(continueLabel).performClick()
    }
}

ComponentActivity muss der Datei AndroidManifest.xml Ihrer App hinzugefügt werden. Aktivieren Sie die Funktion, indem Sie Ihrem Modul die folgende Abhängigkeit hinzufügen:

debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

Benutzerdefinierte semantische Eigenschaften

Sie können benutzerdefinierte Semantik-Attribute erstellen, um Informationen für Tests verfügbar zu machen. Dazu definieren Sie eine neue SemanticsPropertyKey und stellen sie über die SemanticsPropertyReceiver zur Verfügung.

// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey

Verwenden Sie diese Property nun im Modifikator semantics:

val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
    modifier = Modifier.semantics { pickedDate = datePickerValue }
)

Verwenden Sie in Tests SemanticsMatcher.expectValue, um den Wert der Eigenschaft zu bestätigen:

composeTestRule
    .onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
    .assertExists()

Statuswiederherstellung überprüfen

Prüfen Sie, ob der Status Ihrer Compose-Elemente korrekt wiederhergestellt wird, wenn die Aktivität oder der Prozess neu erstellt wird. Führen Sie solche Prüfungen durch, ohne sich auf die Neuerstellung von Aktivitäten mit der Klasse StateRestorationTester zu verlassen.

Mit dieser Klasse können Sie die Neuerstellung einer Composable-Funktion simulieren. Das ist besonders nützlich, um die Implementierung von rememberSaveable zu überprüfen.


class MyStateRestorationTests {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun onRecreation_stateIsRestored() {
        val restorationTester = StateRestorationTester(composeTestRule)

        restorationTester.setContent { MainScreen() }

        // TODO: Run actions that modify the state

        // Trigger a recreation
        restorationTester.emulateSavedInstanceStateRestore()

        // TODO: Verify that state has been correctly restored.
    }
}

Verschiedene Gerätekonfigurationen testen

Android-Apps müssen sich an viele sich ändernde Bedingungen anpassen, z. B. Fenstergrößen, Sprachen, Schriftgrößen, dunkle und helle Designs. Die meisten dieser Bedingungen werden aus Werten auf Geräteebene abgeleitet, die vom Nutzer gesteuert und mit der aktuellen Configuration-Instanz bereitgestellt werden. Das Testen verschiedener Konfigurationen direkt in einem Test ist schwierig, da im Test Eigenschaften auf Geräteebene konfiguriert werden müssen.

DeviceConfigurationOverride ist eine API, die nur für Tests verwendet werden kann. Mit ihr lassen sich verschiedene Gerätekonfigurationen für die zu testenden @Composable-Inhalte lokal simulieren.

Das Companion-Objekt von DeviceConfigurationOverride hat die folgenden Erweiterungsfunktionen, die Konfigurationseigenschaften auf Geräteebene überschreiben:

Wenn Sie einen bestimmten Override anwenden möchten, umschließen Sie den zu testenden Inhalt mit einem Aufruf der Funktion DeviceConfigurationOverride() auf oberster Ebene und übergeben Sie den anzuwendenden Override als Parameter.

Mit dem folgenden Code wird beispielsweise die DeviceConfigurationOverride.ForcedSize()-Überschreibung angewendet, um die Dichte lokal zu ändern. Dadurch wird erzwungen, dass die MyScreen-Composable in einem großen Querformatfenster gerendert wird, auch wenn das Gerät, auf dem der Test ausgeführt wird, diese Fenstergröße nicht direkt unterstützt:

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
}

Wenn Sie mehrere Überschreibungen gleichzeitig anwenden möchten, verwenden Sie DeviceConfigurationOverride.then():

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.FontScale(1.5f) then
            DeviceConfigurationOverride.FontWeightAdjustment(200)
    ) {
        Text(text = "text with increased scale and weight")
    }
}

Benutzerdefinierte Fehlerbehandlung in Tests

Für das Debuggen von UI-Tests ist es oft erforderlich, den Bildschirmstatus und die Kompositionshierarchie zu prüfen, wenn eine Assertion fehlschlägt.

Compose bietet eine native Pipeline zur Fehlerbehandlung, um die Diagnose zu optimieren. Sie können automatisch Screenshots und UI-Hierarchien bei einem Fehler aufnehmen oder benutzerdefinierte Handler registrieren, um Crash-Telemetrie an externe Tools weiterzuleiten.

Fehlerbehandlung konfigurieren

Um die Fehlerbehandlung zu konfigurieren, übergeben Sie ein TestFailurePolicy-Objekt an ComposeUiTestConfig.

Integrierte Fehlerartefakte erfassen

Wenn Sie bei einem fehlgeschlagenen Test Screenshots und UI-Hierarchien erfassen möchten, legen Sie die Eigenschaften screenshotCaptureMode und uiHierarchyCaptureMode in TestFailurePolicy fest.

class MyComposeTest {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled
        )
    )

    @Test
    fun myFirstTest() = runComposeUiTest(config = customConfig) {
        // ...
    }
}

Benutzerdefinierten Fehler-Handler verwenden

Wenn Sie benutzerdefiniertes Verhalten für Testfehler definieren möchten, fügen Sie der failureHandlers-Liste ein TestFailureHandler hinzu. Jeder Handler erhält ein FailureContext-Objekt, das den ursprünglichen Fehler und eine Liste der Artefakte enthält, die von den vorherigen Pipeline-Handlern generiert wurden.

class MyComposeTestWithHandler {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            failureHandlers = listOf(
                TestFailureHandler { context ->
                    val screenshot = context.artifacts.firstOrNull {
                        it.type == FailureArtifact.Type.Screenshot
                    }
                    // ...
                }
            )
        )
    )

    @get:Rule
    val rule = createComposeRule(config = customConfig)

    // ...
}

Aufnahmen für eine gesamte Testsuite konfigurieren

Sie können die Erfassung von Fehlerartefakten für eine gesamte Testsuite aktivieren, indem Sie Argumente für den Instrumentierungs-Runner in der build.gradle-Datei Ihres Moduls übergeben. Dadurch müssen einzelne Testdateien nicht mehr aktualisiert werden.

// build.gradle.kts
android {
    defaultConfig {
        // ...
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
    }
}

Rangfolge der Konfiguration

Das Framework wertet sowohl die lokalen TestFailurePolicy- als auch die Runner-Argumente auf Suite-Ebene aus:

  • Wenn Sie in Ihrem Test failurePolicy explizit einen Modus auf CaptureMode.Enabled/Disabled festlegen, wird das Runner-Argument überschrieben.
  • Wenn Sie einen Modus auf CaptureMode.Unspecified setzen (oder failurePolicy auf null belassen), greift das Framework auf das Runner-Argument zurück.

Zusätzliche Ressourcen

  • Apps auf Android testen: Auf der Haupt-Landingpage für Android-Tests finden Sie einen umfassenderen Überblick über die Grundlagen und Techniken des Testens.
  • Grundlagen des Testens: Hier finden Sie weitere Informationen zu den grundlegenden Konzepten für das Testen einer Android-App.
  • Lokale Tests: Einige Tests können lokal auf Ihrer Workstation ausgeführt werden.
  • Instrumentierte Tests: Es empfiehlt sich, auch instrumentierte Tests auszuführen. Das sind Tests, die direkt auf dem Gerät ausgeführt werden.
  • Continuous Integration: Mit Continuous Integration können Sie Ihre Tests in Ihre Bereitstellungspipeline einbinden.
  • Verschiedene Bildschirmgrößen testen: Da Nutzer so viele verschiedene Geräte zur Verfügung haben, sollten Sie verschiedene Bildschirmgrößen testen.
  • Espresso: Obwohl Espresso für ansichtsbasierte UIs gedacht ist, kann es auch für einige Aspekte von Compose-Tests hilfreich sein.