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

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

DeviceConfigurationOverride

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 facteur de forme de petit téléphone pour exécuter tous vos tests d'UI, y compris ceux 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 une mise en page de tablette à un appareil de plus petit format, 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 souhaitez tester sur différentes tailles de fenêtre.

Robolectric

Utilisez Robolectric pour exécuter des tests d'UI Compose ou basés sur des vues sur la JVM localement, sans appareil ni émulateur. Vous pouvez configurer Robolectric pour utiliser des tailles d'écran spécifiques, entre autres propriétés utiles.

Dans l'exemple suivant 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 du test, comme 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 du système et de l'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 sur la configuration des appareils Robolectric.

Appareils gérés par Gradle

Le plug-in Android Gradle 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 de différentes tailles d'écran afin de mettre en œuvre 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 sont exécutés lorsque cela est nécessaire, en provisionnant et en lançant des émulateurs, et en simplifiant votre configuration 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 parc d'appareils similaire pour exécuter vos tests sur des appareils physiques spécifiques auxquels vous n'avez peut-être pas accès, comme 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 Adapter vos tests grâce aux appareils gérés par Gradle.

Filtrage des tests avec l'exécuteur de tests

Une stratégie de test optimale ne doit pas vérifier deux fois la même chose. Par conséquent, 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'UI en exécutant la totalité ou la plupart d'entre eux sur un facteur de forme de téléphone, et seulement un sous-ensemble sur des appareils de différentes tailles d'écran.

Vous pouvez annoter certains tests pour qu'ils ne soient exécutés que sur 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

et les utiliser 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 des émulateurs sur l'intégration continue, 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 dans les tests à l'aide de n'importe quel type de test 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 le basculement entre les états ou les postures 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 pour 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 qu'Espresso Device est encore en phase alpha et qu'il est soumis aux exigences suivantes :

  • Plug-in Android Gradle 8.3 ou version ultérieure
  • Android Emulator 33.1.10 ou version ultérieure
  • Un 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 RequiresDeviceMode

L'annotation RequiresDeviceMode peut être utilisée plusieurs fois pour indiquer qu'un test 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()))
    }
}

Annotation RequiresDisplay

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 des buckets de dimensions en fonction des 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 la méthode conjointement avec la classe DisplaySizeRule, qui permet de s'assurer que toutes les modifications apportées lors des tests sont annulées avant le test suivant.

@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 correspond pas à l'appareil cible, le test échoue avec une 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 qui implique 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 Window Testing

La bibliothèque Window Testing contient des utilitaires qui vous aident à écrire des tests qui s'appuient sur des fonctionnalités liées à la gestion des fenêtres, telles que l'intégration d'activités ou les fonctionnalités pliables, ou qui les vérifient. 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 Compose, vous pouvez implémenter FoldingFeature de la manière suivante :

@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 d'UI à 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 bien celle attendue :

Nouveau message

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