Bibliotecas e ferramentas para testar diferentes tamanhos de tela

O Android oferece uma variedade de ferramentas e APIs que podem ajudar você a criar testes para diferentes tamanhos de tela e janela.

DeviceConfigurationOverride

O elemento combinável DeviceConfigurationOverride permite substituir atributos de configuração para testar vários tamanhos de tela e janela no Compose layouts. A substituição de ForcedSize se encaixa em qualquer layout no espaço disponível, que permite executar Teste de interface em qualquer tamanho de tela. Por exemplo, é possível usar um formato de smartphone pequeno para executar todos os seus testes de interface, incluindo testes de interface para smartphones grandes, dobráveis e tablets.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
Figura 1. Usar DeviceConfigurationOverride para ajustar um layout de tablet em um dispositivo com formato menor, como em \*Now in Android*.

Além disso, é possível usar essa função para definir a escala da fonte, temas e outros propriedades que você pode querer testar em diferentes tamanhos de janela.

Robolectric

Use o Robolectric (link em inglês) para executar testes de interface do Compose ou baseados em visualização na JVM. localmente: sem dispositivos ou emuladores necessários. Você pode configurar Robolectric para usar tamanhos de tela específicos, entre outras propriedades úteis.

No exemplo a seguir do Now in Android (links em inglês), o Robolectric está configurado para emular uma tela de 1000x1000 dp com resolução 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 { ... }

Você também pode definir os qualificadores do corpo do teste, como feito neste snippet de o exemplo do Now in Android:

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

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

Observe que o RuntimeEnvironment.setQualifiers() atualiza o sistema e recursos do aplicativo com a nova configuração, mas não aciona nenhuma ação em atividades ativas ou outros componentes.

Saiba mais na documentação Configuração de dispositivos do Robolectric.

Dispositivos gerenciados pelo Gradle

O Plug-in do Android para Gradle dos dispositivos gerenciados pelo Gradle (GMD, na sigla em inglês) permite definir as especificações dos emuladores e dispositivos reais em que que seus testes instrumentados sejam executados. Criar especificações para dispositivos com diferentes tamanhos de tela para implementar uma estratégia de teste em que certos testes precisam ser executados em determinados tamanhos de tela. Usando o GMD com integração contínua (CI), você pode garantir que os testes apropriados sejam executados quando necessário, provisionar e iniciar emuladores e simplificar a configuração da 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"
                }
            }
        }
    }
}

Você pode encontrar vários exemplos de GMD no projeto testing-samples.

Firebase Test Lab

Use o Firebase Test Lab (FTL, na sigla em inglês) ou um serviço de farm de dispositivos semelhante para executar sua testes em dispositivos reais específicos aos quais você pode não ter acesso, como dobráveis ou tablets de tamanhos variados. O Firebase Test Lab é um serviço com um nível sem custo financeiro. O FTL também oferece suporte à execução de testes em emuladores. Esses serviços melhoram a confiabilidade e a velocidade dos testes instrumentados porque é possível provisionar dispositivos e emuladores com antecedência.

Para saber mais sobre como usar o FTL com o GMD, consulte Escalonar seus testes com Dispositivos gerenciados pelo Gradle.

Testar a filtragem com o executor de testes

Uma estratégia de teste ideal não deve verificar a mesma coisa duas vezes, então a maioria dos seus Os testes de interface não precisam ser executados em vários dispositivos. Normalmente, você filtra a interface executando todos ou a maioria deles em um formato de smartphone e apenas um subconjunto dispositivos com diferentes tamanhos de tela.

É possível fazer anotações em determinados testes para que eles sejam executados apenas com determinados dispositivos e depois passar um argumento para o AndroidJUnitRunner usando o comando que executa a provas.

Por exemplo, você pode criar anotações diferentes:

annotation class TestExpandedWidth
annotation class TestCompactWidth

Use-os em diferentes testes:

class MyTestClass {

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

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

}

Em seguida, use o android.testInstrumentationRunnerArguments.annotation ao executar os testes para filtrar aqueles específicos. Por exemplo, se você estiver usando dispositivos gerenciados pelo Gradle:

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

Se você não usa o GMD e gerencia emuladores em CI, primeiro verifique se o o emulador ou dispositivo correto está pronto e conectado e, em seguida, transmita o parâmetro a um dos comandos do Gradle para executar testes instrumentados:

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

Observe que o dispositivo Espresso (consulte a próxima seção) também pode filtrar testes usando propriedades do dispositivo.

Dispositivo Espresso

Use o Espresso Device para executar ações em emuladores em testes usando qualquer tipo de testes instrumentados, incluindo testes do Espresso, Compose ou UI Automator. Essas ações podem incluir definir o tamanho da tela ou alternar os estados dobráveis. ou posturas. Por exemplo, você pode controlar um emulador dobrável e defini-lo como o modo de mesa. O dispositivo Espresso também contém regras JUnit e anotações para exigem determinados recursos:

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

Observe que o Espresso Device ainda está no estágio alfa e tem os seguintes requisitos:

  • Plug-in do Android para Gradle 8.3 ou mais recente
  • Android Emulator 33.1.10 ou mais recente
  • Dispositivo virtual Android com o nível 24 da API ou mais recente

Filtrar testes

O dispositivo Espresso pode ler as propriedades dos dispositivos conectados para permitir que você filtrar testes usando anotações. Se os requisitos anotados não forem atendidos, os testes serão pulados.

Anotação ExigeDeviceMode

A anotação RequiresDeviceMode pode ser usada várias vezes para indicar um teste que será executado somente se todos os valores de DeviceMode forem compatíveis no dispositivo.

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

Anotação Requer Display

A anotação RequiresDisplay permite especificar a largura e a altura dos a tela do dispositivo usando classes de tamanho, que definem intervalos de dimensão. seguindo as classes de tamanho de janela oficiais.

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

Redimensionar telas

Use o método setDisplaySize() para redimensionar as dimensões da tela. durante a execução. Use o método em conjunto com DisplaySizeRule. , o que garante que todas as alterações feitas durante os testes sejam desfeitas antes que a próximo teste.

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

Ao redimensionar uma tela com setDisplaySize(), a densidade não é afetada do dispositivo. Assim, se uma dimensão não couber no dispositivo de destino, o falha com um UnsupportedDeviceOperationException. Para evitar que os testes em execução nesse caso, use a anotação RequiresDisplay para filtrá-las:

@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

A classe StateRestorationTester (link em inglês) é usada para testar a restauração de estado. para componentes combináveis sem recriar atividades. Isso deixa os testes mais rápidos e mais confiável, já que a recriação de atividades é um processo complexo com várias mecanismos de sincronização:

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

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

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

Biblioteca de teste de janela

A biblioteca Window Testing contém utilitários para ajudar você a programar testes que dependem ativar ou verificar recursos relacionados ao gerenciamento de janelas, como atividade embeddings ou recursos dobráveis. O artefato está disponível no site do Google Repositório Maven.

Por exemplo, é possível usar a função FoldingFeature() para gerar uma FoldingFeature personalizada, que pode ser usada nas visualizações do Compose. Em Java, use a função createFoldingFeature().

Em uma visualização do Compose, você pode implementar FoldingFeature da seguinte maneira:

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

Além disso, é possível emular recursos de exibição em testes de interface usando o função TestWindowLayoutInfo(). O exemplo a seguir simula um FoldingFeature com um HALF_OPENED articulação vertical no centro da tela e verifica se o é o esperado:

Escrever

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

Visualizações

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

Confira mais exemplos no projeto WindowManager (link em inglês).

Outros recursos

Documentação

Amostras

Codelabs