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 de 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 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 de plus petit facteur de forme, comme dans \*Now in Android*.

Vous pouvez également 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 localement des tests d'interface utilisateur basés sur les vues ou Compose sur la JVM. Aucun appareil ni émulateur n'est requis. Vous pouvez, par exemple, configurer Robolectric pour utiliser des tailles d'écran spécifiques.

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 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 les 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 Gradle-managed devices (GMD) vous permet de définir les spécifications des émulateurs et des appareils réels sur lesquels vos tests d'instrumentation sont exécutés. Créez des spécifications pour les appareils dotés de tailles d'écran différentes afin d'implémenter une stratégie de test selon laquelle certains tests doivent être exécutés sur des tailles d'écran données. 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, en provisionnant et en lançant des émulateurs, et en simplifiant la configuration de la 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 une version 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 avec des appareils gérés par Gradle.

Tester le filtrage avec le lanceur de test

Une stratégie de test optimale ne doit pas vérifier la même chose deux fois. Par conséquent, la plupart de vos tests d'interface utilisateur n'ont pas besoin de s'exécuter sur plusieurs appareils. En règle générale, vous filtrez vos tests d'interface utilisateur en les exécutant tous ou la plupart sur un facteur de forme de téléphone et uniquement 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 lors de 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 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 à expresso

Utilisez Espresso Device pour effectuer des actions sur les émulateurs dans les 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 ou 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 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 que l'appareil Espresso est encore en phase alpha et qu'il doit répondre aux 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.

Nécessite l'annotation DeviceMode

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()))
    }
}

Annotation RequiredDisplay

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 conformes aux 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 conjointement avec la classe DisplaySizeRule pour vous 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. Dans ce cas, pour empêcher l'exécution de tests, filtrez-les à l'aide de l'annotation RequiresDisplay:

@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.
    }
}

Testeur de restauration d'État

La classe StateRestorationTester permet de tester la restauration d'état pour les 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 Window Testing

La bibliothèque Window Testing contient des utilitaires qui vous aident à écrire des tests qui reposent sur ou vérifient des fonctionnalités liées à la gestion des fenêtres, telles que l'intégration d'activités ou les fonctionnalités 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 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 de l'interface utilisateur à l'aide de la fonction TestWindowLayoutInfo(). L'exemple suivant simule un 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