Bibliothèques et outils pour tester différentes tailles d'écran

Android fournit divers outils et API qui peuvent vous aider à créer des tests pour différentes tailles d'écran et de fenêtre.

Remplacement de la configuration de l'appareil

.

Le composable DeviceConfigurationOverride vous permet de remplacer les attributs de configuration pour tester plusieurs tailles d'écran et de fenêtre dans les mises en page Compose. Le forçage ForcedSize s'adapte à n'importe quelle mise en page dans l'espace disponible, ce qui vous permet d'exécuter n'importe quel test d'interface utilisateur sur n'importe quelle taille d'écran. Par exemple, vous pouvez utiliser un petit facteur de forme de téléphone pour exécuter tous vos tests d'interface utilisateur, y compris les tests d'interface utilisateur pour les grands téléphones, les pliables et les tablettes.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Figure 1. Utilisation de DeviceConfigurationOverride pour adapter la mise en page d'une tablette à un appareil plus petit, comme dans \*Now in Android*.

De plus, vous pouvez utiliser ce composable pour définir l'échelle de police, les thèmes et d'autres propriétés que vous pouvez tester sur différentes tailles de fenêtre.

Robolectric

Utilisez Robolectric pour exécuter des tests d'interface utilisateur basés sur les vues ou Compose sur la JVM en local, sans appareil ni émulateur. Vous pouvez configurer Robolectric pour qu'il utilise des tailles d'écran spécifiques, entre autres propriétés utiles.

Dans l'exemple suivant tiré de Now in Android, Robolectric est configuré pour émuler une taille d'écran de 1 000 x 1 000 dp avec une résolution de 480 ppp:

@RunWith(RobolectricTestRunner::class)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(qualifiers = "w1000dp-h1000dp-480dpi")
class NiaAppScreenSizesScreenshotTests { ... }

Vous pouvez également définir les qualificatifs à partir du corps de test comme indiqué dans cet extrait de l'exemple Now in Android:

val (width, height, dpi) = ...

// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")

Notez que RuntimeEnvironment.setQualifiers() met à jour les ressources système et d'application avec la nouvelle configuration, mais ne déclenche aucune action sur les activités actives ou d'autres composants.

Pour en savoir plus, consultez la documentation de Robolectric sur la configuration de l'appareil.

Appareils gérés par Gradle

Le plug-in Android Gradle pour les appareils gérés par Gradle (GMD) vous permet de définir les spécifications des émulateurs et des appareils réels sur lesquels vos tests instrumentés sont exécutés. Créez des spécifications pour les appareils dotés de différentes tailles d'écran afin d'implémenter une stratégie de test dans laquelle certains tests doivent être exécutés sur certaines tailles d'écran. En utilisant GMD avec l'intégration continue (CI), vous pouvez vous assurer que les tests appropriés s'exécutent en cas de besoin, provisionner et lancer des émulateurs, et simplifier la configuration de votre CI.

android {
    testOptions {
        managedDevices {
            devices {
                // Run with ./gradlew nexusOneApi30DebugAndroidTest.
                nexusOneApi30(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Nexus One"
                    apiLevel = 30
                    // Use the AOSP ATD image for better emulator performance
                    systemImageSource = "aosp-atd"
                }
                // Run with ./gradlew  foldApi34DebugAndroidTest.
                foldApi34(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Pixel Fold"
                    apiLevel = 34
                    systemImageSource = "aosp-atd"
                }
            }
        }
    }
}

Vous trouverez plusieurs exemples de GMD dans le projet testing-samples.

Firebase Test Lab

Utilisez Firebase Test Lab (FTL) ou un service de ferme d'appareils similaire pour exécuter vos tests sur des appareils réels spécifiques auxquels vous n'avez peut-être pas accès, tels que des pliables ou des tablettes de différentes tailles. Firebase Test Lab est un service payant avec un niveau sans frais. FTL permet également d'exécuter des tests sur des émulateurs. Ces services améliorent la fiabilité et la vitesse des tests d'instrumentation, car ils peuvent provisionner des appareils et des émulateurs à l'avance.

Pour en savoir plus sur l'utilisation de FTL avec GMD, consultez la section Effectuer le scaling de vos tests avec des appareils gérés par Gradle.

Filtrer les tests avec le lanceur de test

Une stratégie de test optimale ne doit pas vérifier la même chose deux fois. Ainsi, la plupart de vos tests d'interface utilisateur n'ont pas besoin d'être exécutés sur plusieurs appareils. En règle générale, vous filtrez vos tests d'interface utilisateur en exécutant la plupart ou la totalité d'entre eux sur un facteur de forme de téléphone et seulement un sous-ensemble sur des appareils avec des tailles d'écran différentes.

Vous pouvez annoter certains tests pour qu'ils ne soient exécutés qu'avec certains appareils, puis transmettre un argument à AndroidJUnitRunner à l'aide de la commande qui exécute les tests.

Par exemple, vous pouvez créer différentes annotations:

annotation class TestExpandedWidth
annotation class TestCompactWidth

Utilisez-les dans différents tests:

class MyTestClass {

    @Test
    @TestExpandedWidth
    fun myExample_worksOnTablet() {
        ...
    }

    @Test
    @TestCompactWidth
    fun myExample_worksOnPortraitPhone() {
        ...
    }

}

Vous pouvez ensuite utiliser la propriété android.testInstrumentationRunnerArguments.annotation lors de l'exécution des tests pour filtrer des tests spécifiques. Par exemple, si vous utilisez des appareils gérés par Gradle:

$ ./gradlew pixelTabletApi30DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

Si vous n'utilisez pas GMD et que vous gérez les émulateurs sur la CI, assurez-vous d'abord que l'émulateur ou l'appareil approprié est prêt et connecté, puis transmettez le paramètre à l'une des commandes Gradle pour exécuter des tests d'instrumentation:

$ ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

Notez qu'Espresso Device (voir la section suivante) peut également filtrer les tests à l'aide des propriétés de l'appareil.

Appareil Espresso

Utilisez Espresso Device pour effectuer des actions sur les émulateurs lors de tests à l'aide de n'importe quel type de tests d'instrumentation, y compris les tests Espresso, Compose ou UI Automator. Ces actions peuvent inclure la définition de la taille de l'écran, ou l'activation/la désactivation des états ou des positions des appareils pliables. Par exemple, vous pouvez contrôler un émulateur d'appareil pliable et le définir en mode sur table. Espresso Device contient également des règles et des annotations JUnit permettant d'exiger certaines fonctionnalités:

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {

    @get:Rule(order=1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    @get:Rule(order=2) val screenOrientationRule: ScreenOrientationRule =
        ScreenOrientationRule(ScreenOrientation.PORTRAIT)

    @Test
    fun tabletopMode_playerIsDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

Notez que l'appareil Espresso est encore en phase alpha et présente les exigences suivantes:

  • Plug-in Android Gradle 8.3 ou version ultérieure
  • Android Emulator 33.1.10 ou version ultérieure
  • Appareil virtuel Android exécutant le niveau d'API 24 ou supérieur

Filtrer les tests

Espresso Device peut lire les propriétés des appareils connectés pour vous permettre de filtrer les tests à l'aide d'annotations. Si les exigences annotées ne sont pas remplies, les tests sont ignorés.

Annotation DeviceMode requise

L'annotation RequiresDeviceMode peut être utilisée plusieurs fois pour indiquer un test qui ne s'exécutera que si toutes les valeurs DeviceMode sont compatibles avec l'appareil.

class OnDeviceTest {
    ...
    @Test
    @RequiresDeviceMode(TABLETOP)
    @RequiresDeviceMode(BOOK)
    fun tabletopMode_playerIdDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

Nécessite une annotation Display

L'annotation RequiresDisplay vous permet de spécifier la largeur et la hauteur de l'écran de l'appareil à l'aide de classes de taille, qui définissent les buckets de dimensions suivant les classes de taille de fenêtre officielles.

class OnDeviceTest {
    ...
    @Test
    @RequiresDisplay(EXPANDED, COMPACT)
    fun myScreen_expandedWidthCompactHeight() {
        ...
    }
}

Redimensionner les écrans

Utilisez la méthode setDisplaySize() pour redimensionner les dimensions de l'écran au moment de l'exécution. Utilisez cette méthode conjointement avec la classe DisplaySizeRule pour vous assurer que toutes les modifications apportées lors des tests sont annulées avant le prochain test.

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) val displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    @Test
    fun resizeWindow_compact() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.COMPACT,
            heightSizeClass = HeightSizeClass.COMPACT
        )
        // Verify visual attributes or state restoration.
    }
}

Lorsque vous redimensionnez un écran avec setDisplaySize(), vous n'affectez pas la densité de l'appareil. Par conséquent, si une dimension ne s'adapte pas à l'appareil cible, le test échoue avec un UnsupportedDeviceOperationException. Pour empêcher l'exécution des tests dans ce cas, utilisez l'annotation RequiresDisplay pour les filtrer:

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) var displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    /**
     * Setting the display size to EXPANDED would fail in small devices, so the [RequiresDisplay]
     * annotation prevents this test from being run on devices outside the EXPANDED buckets.
     */
    @RequiresDisplay(
        widthSizeClass = WidthSizeClassEnum.EXPANDED,
        heightSizeClass = HeightSizeClassEnum.EXPANDED
    )
    @Test
    fun resizeWindow_expanded() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.EXPANDED,
            heightSizeClass = HeightSizeClass.EXPANDED
        )
        // Verify visual attributes or state restoration.
    }
}

StateRestorationTester

La classe StateRestorationTester permet de tester la restauration de l'état des composants composables sans recréer d'activités. Cela rend les tests plus rapides et plus fiables, car la recréation d'activité est un processus complexe comportant plusieurs mécanismes de synchronisation:

@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
    val stateRestorationTester = StateRestorationTester(composeTestRule)

    // Set content through the StateRestorationTester object.
    stateRestorationTester.setContent {
        MyApp()
    }

    // Simulate a config change.
    stateRestorationTester.emulateSavedInstanceStateRestore()
}

Bibliothèque de tests de fenêtres

La bibliothèque Window Testing contient des utilitaires pour vous aider à écrire des tests qui reposent sur ou vérifient les fonctionnalités liées à la gestion des fenêtres, telles que l'intégration d'activités ou les fonctionnalités des appareils pliables. L'artefact est disponible via le dépôt Maven de Google.

Par exemple, vous pouvez utiliser la fonction FoldingFeature() pour générer un FoldingFeature personnalisé, que vous pouvez utiliser dans les aperçus Compose. En Java, utilisez la fonction createFoldingFeature().

Dans un aperçu de Compose, vous pouvez implémenter FoldingFeature comme suit:

@Preview(showBackground = true, widthDp = 480, heightDp = 480)
@Composable private fun FoldablePreview() =
    MyApplicationTheme {
        ExampleScreen(
            displayFeatures = listOf(FoldingFeature(Rect(0, 240, 480, 240)))
        )
 }

Vous pouvez également émuler les fonctionnalités d'affichage dans les tests de l'interface utilisateur à l'aide de la fonction TestWindowLayoutInfo(). L'exemple suivant simule une FoldingFeature avec une charnière verticale HALF_OPENED au centre de l'écran, puis vérifie si la mise en page est celle attendue:

Compose

import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

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

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        composeTestRule.setContent {
            MediaPlayerScreen()
        }

        val hinge = FoldingFeature(
            activity = composeTestRule.activity,
            state = HALF_OPENED,
            orientation = VERTICAL,
            size = 2
        )

        val expected = TestWindowLayoutInfo(listOf(hinge))
        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)

        composeTestRule.waitForIdle()

        // Verify that the folding feature is detected and media controls shown.
        composeTestRule.onNodeWithTag("MEDIA_CONTROLS").assertExists()
    }
}

Vues

import androidx.window.layout.FoldingFeature.Orientation
import androidx.window.layout.FoldingFeature.State
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val activityRule = ActivityScenarioRule(MediaPlayerActivity::class.java)

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        activityRule.scenario.onActivity { activity ->
            val feature = FoldingFeature(
                activity = activity,
                state = State.HALF_OPENED,
                orientation = Orientation.VERTICAL)
            val expected = TestWindowLayoutInfo(listOf(feature))
            windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
        }

        // Verify that the folding feature is detected and media controls shown.
        onView(withId(R.id.media_controls)).check(matches(isDisplayed()))
    }
}

Vous trouverez d'autres exemples dans le projet WindowManager.

Ressources supplémentaires

Documentation

Exemples

Ateliers de programmation