É possível testar seu app do Compose com abordagens e padrões bem estabelecidos.
Testar de forma isolada
A ComposeTestRule permite que você inicie uma atividade mostrando qualquer elemento combinável:
o app inteiro, uma única tela ou um elemento pequeno. Também é uma boa prática verificar se os elementos combináveis estão encapsulados corretamente e se funcionam de forma independente, permitindo testes de UI mais fáceis e focados.
Isso não significa que você só pode criar testes de IU de unidade. Os testes voltados para partes maiores da IU também são muito importantes.
Acessar a atividade e os recursos depois de definir seu conteúdo
Muitas vezes, é necessário definir o conteúdo que está em teste usando
composeTestRule.setContent e também acessar recursos de atividade, por
exemplo, para declarar que um texto mostrado corresponde a um recurso de string. No entanto, não é possível chamar setContent em uma regra criada com createAndroidComposeRule() se a atividade já o tiver chamado.
Um padrão comum para fazer isso é criar uma AndroidComposeTestRule usando
uma atividade vazia, como ComponentActivity.
class MyComposeTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
@Test
fun myTest() {
// Start the app
composeTestRule.setContent {
MyAppTheme {
MainScreen(uiState = exampleUiState, /*...*/)
}
}
val continueLabel = composeTestRule.activity.getString(R.string.next)
composeTestRule.onNodeWithText(continueLabel).performClick()
}
}
Observe que a ComponentActivity precisa ser adicionada ao arquivo
AndroidManifest.xml do app. Para ativar, adicione esta dependência ao
módulo:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
Propriedades de semântica personalizadas
É possível criar propriedades semânticas personalizadas para expor informações aos testes.
Para fazer isso, defina um novo SemanticsPropertyKey e disponibilize-o usando o
SemanticsPropertyReceiver.
// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey
Agora use essa propriedade no modificador semantics:
val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
modifier = Modifier.semantics { pickedDate = datePickerValue }
)
Após os testes, use SemanticsMatcher.expectValue para declarar o valor
da propriedade:
composeTestRule
.onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
.assertExists()
Verificar a restauração do estado
Verifique se o estado dos elementos do Compose é restaurado corretamente quando a
atividade ou o processo são recriados. Faça essas verificações sem depender da recriação de
atividades com a classe StateRestorationTester.
Essa classe permite simular a recriação de um elemento combinável. Ela é especialmente
útil para verificar a implementação de rememberSaveable.
class MyStateRestorationTests {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun onRecreation_stateIsRestored() {
val restorationTester = StateRestorationTester(composeTestRule)
restorationTester.setContent { MainScreen() }
// TODO: Run actions that modify the state
// Trigger a recreation
restorationTester.emulateSavedInstanceStateRestore()
// TODO: Verify that state has been correctly restored.
}
}
Testar diferentes configurações de dispositivo
Os apps Android precisam se adaptar a muitas condições variáveis: tamanhos de janela, locais,
tamanhos de fonte, temas escuro e claro e muito mais. A maioria dessas condições é derivada de valores no nível do dispositivo controlados pelo usuário e expostos com a instância Configuration atual. Testar diferentes configurações diretamente em um teste é difícil, já que o teste precisa configurar propriedades no nível do dispositivo.
DeviceConfigurationOverride é uma API somente para testes que permite simular
diferentes configurações de dispositivos de maneira localizada para o conteúdo @Composable
em teste.
O objeto complementar de DeviceConfigurationOverride tem as seguintes
funções de extensão, que substituem as propriedades de configuração no nível do dispositivo:
DeviceConfigurationOverride.DarkMode(): substitui o sistema pelo tema escuro ou claro.DeviceConfigurationOverride.FontScale(): substitui a escala da fonte do sistema.DeviceConfigurationOverride.FontWeightAdjustment(): substitui o ajuste de espessura da fonte do sistema.DeviceConfigurationOverride.ForcedSize(): força uma quantidade específica de espaço, independente do tamanho do dispositivo.DeviceConfigurationOverride.LayoutDirection(): substitui a direção do layout (da esquerda para a direita ou da direita para a esquerda).DeviceConfigurationOverride.Locales(): substitui a localidade.DeviceConfigurationOverride.RoundScreen(): substitui se a tela for redonda.
Para aplicar uma substituição específica, encapsule o conteúdo em teste em uma chamada para a função de nível superior
DeviceConfigurationOverride(), transmitindo a substituição
a ser aplicada como um parâmetro.
Por exemplo, o código a seguir aplica a substituição
DeviceConfigurationOverride.ForcedSize() para mudar a densidade
localmente, forçando o elemento combinável MyScreen a ser renderizado em uma janela
horizontal grande, mesmo que o dispositivo em que o teste está sendo executado não ofereça suporte a esse tamanho de janela
diretamente:
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp)) ) { MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping. } }
Para aplicar várias substituições juntas, use
DeviceConfigurationOverride.then():
composeTestRule.setContent { DeviceConfigurationOverride( DeviceConfigurationOverride.FontScale(1.5f) then DeviceConfigurationOverride.FontWeightAdjustment(200) ) { Text(text = "text with increased scale and weight") } }
Processamento de falhas personalizadas em testes
A depuração de testes de interface geralmente exige a inspeção do estado da tela e da hierarquia de composição quando uma asserção falha.
O Compose oferece um pipeline nativo de tratamento de falhas para simplificar os diagnósticos. É possível capturar automaticamente capturas de tela e hierarquias de interface em caso de falha ou registrar manipuladores personalizados para encaminhar a telemetria de falhas a ferramentas externas.
Configurar o tratamento de falhas
Para configurar o tratamento de falhas, transmita um objeto TestFailurePolicy para
ComposeUiTestConfig.
Capturar artefatos de falha integrados
Para capturar capturas de tela e hierarquias de interface em caso de falha no teste, defina as propriedades
screenshotCaptureMode e uiHierarchyCaptureMode em
TestFailurePolicy.
class MyComposeTest { private val customConfig = ComposeUiTestConfig( failurePolicy = TestFailurePolicy( screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled ) ) @Test fun myFirstTest() = runComposeUiTest(config = customConfig) { // ... } }
Usar um gerenciador de falhas personalizado
Para definir um comportamento personalizado para falhas de teste, adicione um
TestFailureHandler à lista failureHandlers. Cada manipulador
recebe um objeto FailureContext que contém o erro original e uma
lista de artefatos gerados pelos manipuladores de pipeline anteriores.
class MyComposeTestWithHandler { private val customConfig = ComposeUiTestConfig( failurePolicy = TestFailurePolicy( screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled, failureHandlers = listOf( TestFailureHandler { context -> val screenshot = context.artifacts.firstOrNull { it.type == FailureArtifact.Type.Screenshot } // ... } ) ) ) @get:Rule val rule = createComposeRule(config = customConfig) // ... }
Configurar capturas para um conjunto de testes inteiro
É possível ativar capturas de artefatos de falha para todo um conjunto de testes transmitindo
argumentos do executor de instrumentação no arquivo build.gradle do módulo. Isso elimina a necessidade de atualizar arquivos de teste individuais.
// build.gradle.kts
android {
defaultConfig {
// ...
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
}
}
Precedência de configuração
O framework avalia os argumentos do executor local TestFailurePolicy e da suíte:
- Se você definir explicitamente um modo como
CaptureMode.Enabled/Disabledno testefailurePolicy, isso vai substituir o argumento do executor. - Se você definir um modo como
CaptureMode.Unspecifiedou deixarfailurePolicycomonull, o framework vai usar o argumento do executor.
Outros recursos
- Testar apps no Android: a página de destino principal de testes do Android oferece uma visão mais ampla dos princípios básicos e das técnicas de teste.
- Fundamentos dos testes:saiba mais sobre os conceitos básicos por trás do teste de um app Android.
- Testes locais:é possível executar alguns testes localmente, na sua própria estação de trabalho.
- Testes instrumentados:é uma boa prática executar também testes instrumentados. Ou seja, testes executados diretamente no dispositivo.
- Integração contínua:permite integrar seus testes ao pipeline de implantação.
- Teste diferentes tamanhos de tela:com tantos dispositivos disponíveis para os usuários, é importante testar diferentes tamanhos de tela.
- Espresso: embora seja destinado a interfaces baseadas em visualizações, o conhecimento do Espresso ainda pode ser útil para alguns aspectos dos testes do Compose.