É 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, como o app inteiro, uma única tela ou um elemento pequeno. Também é uma boa
prática verificar se os elementos que podem ser compostos estão encapsulados corretamente e funcionam
de forma independente, o que facilita e foca os testes de interface.
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 vai ser possível chamar o setContent em uma regra criada com createAndroidComposeRule() se
ele já tiver sido chamado pela atividade.
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 de semântica 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 com a classe StateRestorationTester sem depender da recriação de atividades.
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, localidades,
tamanhos de fonte, temas escuros e claros 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 atual de Configuration. Testar diferentes configurações
diretamente em um teste é difícil, porque o teste precisa configurar propriedades
no nível do dispositivo.
DeviceConfigurationOverride é uma API somente para testes que permite simular diferentes configuradores do dispositivo 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 de 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, independentemente 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, envolva 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 de paisagem
grande, mesmo que o dispositivo em que o teste está sendo executado não ofereça suporte direto a esse tamanho
de janela:
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") } }
Tratamento de falhas personalizado 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 declaraçã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 IU em caso de falha ou registrar gerenciadores personalizados para encaminhar a telemetria de falhas para 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 gerenciador
recebe um objeto FailureContext que contém o erro original e uma
lista de artefatos gerados pelos gerenciadores 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 pacote de testes inteiro
É possível ativar as capturas de artefatos de falha para um pacote de testes inteiro 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 do nível do
conjunto:
- 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.Unspecified(ou 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 conceitos básicos e das técnicas de teste.
- Conceitos básicos de 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 de instrumentação:também é uma boa prática executar testes de instrumentação. Ou seja, testes que são executados diretamente no dispositivo.
- Integração contínua:com a integração contínua, é possível 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.