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 téléphones 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.
}
De plus, vous pouvez utiliser ce composable pour définir l'échelle de la 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'interface utilisateur basés sur Compose ou sur les vues sur la JVM localement, 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 dpi:
@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 système et d'application avec la nouvelle configuration, mais ne déclenche aucune action sur les activités actives ni sur d'autres composants.
Pour en savoir plus, consultez la documentation Configuration de l'appareil de 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 d'instrumentation 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 lorsque cela est nécessaire, en provisionnant et en lançant des émulateurs, et en simplifiant 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 physiques spécifiques auxquels vous n'avez peut-être pas accès, tels que des appareils 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 rapidité 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.
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. 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'interface utilisateur en exécutant tout ou partie d'entre eux 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
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
lorsque vous exécutez les 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 un IC, 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 instrumentés:
$ ./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 utilisant 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. L'appareil Espresso 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 présente les 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 respectées, les tests sont ignorés.
Annotation RequiresDeviceMode
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 la méthode avec la classe DisplaySizeRule
, qui s'assure 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 rentre pas dans l'appareil cible, le test échoue avec une erreur 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 avec 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 de test des fenêtres contient des utilitaires pour vous aider à écrire des tests qui s'appuient sur des fonctionnalités liées à la gestion des fenêtres ou qui les vérifient, telles que l'intégration d'activités ou les fonctionnalités pliables. L'artefact est disponible dans 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
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 d'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 bien 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
- Consignes relatives à la qualité des applications sur grand écran
- Tester des applications sur Android
- Tester votre mise en page Compose
Exemples
- Exemple WindowManager
- Exemples d'appareils Espresso
- En ce moment sur Android
- Utilise des tests de captures d'écran pour vérifier différentes tailles d'écran
Ateliers de programmation