Usar cópias de teste no Android

Quando você testa um elemento ou sistema de elementos, faz isso em isolamento. Por exemplo, para testar um ViewModel, você não precisa iniciar um emulador e lançar uma interface porque ele não depende (ou não deveria depender) do framework do Android.

No entanto, o sujeito em teste pode depender de outros para funcionar. Por exemplo, um ViewModel pode depender de um repositório de dados para funcionar.

Quando você precisa fornecer uma dependência a um objeto em teste, uma prática comum é criar uma cópia de teste (ou um objeto de teste). Test doubles são objetos que parecem e agem como componentes no app, mas são criados no teste para fornecer um comportamento ou dados específicos. As principais vantagens são que eles tornam seus testes mais rápidos e simples.

Tipos de duplas de teste

Há vários tipos de duplas de teste:

Falso Um duplo de teste que tem uma implementação "funcional" da classe, mas que é implementado de uma maneira que o torna bom para testes, mas inadequado para produção.

Exemplo: um banco de dados na memória.

Os fakes não exigem um framework de simulação e são leves. Eles são preferencial.

Modelo Um duplo de teste que se comporta como você programou e que tem expectativas sobre as interações. Os modelos vão falhar nos testes se as interações não corresponderem aos requisitos definidos. Os modelos geralmente são criados com um mocking framework para conseguir tudo isso.

Exemplo: verifique se um método em um banco de dados foi chamado exatamente uma vez.

Stub Um duplo de teste que se comporta como você programou, mas não tem expectativas sobre as interações. Geralmente criado com um framework de simulação. As falsificações são preferidas em vez de stubs para simplificar.
Fictício Um duplo de teste que é transmitido, mas não usado, como se você precisasse apenas fornecê-lo como um parâmetro.

Exemplo: uma função vazia transmitida como um callback de clique.

Espião Um wrapper sobre um objeto real que também rastreia algumas informações adicionais, semelhante a mocks. Elas geralmente são evitadas porque adicionam complexidade. Portanto, é melhor usar falsificações ou simulações em vez de espiões.
Sombra Fake usado no Robolectric.

Exemplo usando uma falsificação

Suponha que você queira fazer um teste unitário de um ViewModel que depende de uma interface chamada UserRepository e expõe o nome do primeiro usuário para uma interface. É possível criar um double de teste falso implementando a interface e retornando dados conhecidos.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

Esse UserRepository falso não precisa depender das fontes de dados locais e remotas que a versão de produção usaria. O arquivo está no conjunto de origem de teste e não é enviado com o app de produção.

Uma dependência falsa pode retornar dados conhecidos sem depender de fontes de dados remotas.
Figura 1: uma dependência falsa em um teste de unidade.

O teste a seguir verifica se o ViewModel expõe corretamente o nome do primeiro usuário à visualização.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

Substituir o UserRepository por um falso é fácil em um teste de unidade, porque o ViewModel é criado pelo testador. No entanto, pode ser difícil substituir elementos arbitrários em testes maiores.

Substituição de componentes e injeção de dependência

Quando os testes não têm controle sobre a criação dos sistemas em teste, substituir componentes por duplos de teste se torna mais complexo e exige que a arquitetura do app siga um design testável.

Até mesmo testes de ponta a ponta grandes podem se beneficiar do uso de duplos de teste, como um teste de interface instrumentado que navega por um fluxo de usuário completo no app. Nesse caso, talvez seja melhor tornar o teste hermético. Um teste hermético evita todas as dependências externas, como a busca de dados da Internet. Isso melhora a confiabilidade e o desempenho.

Figura 2: um teste grande que abrange a maior parte do app e simula dados remotos.

É possível projetar o app para alcançar essa flexibilidade manualmente, mas recomendamos usar um framework de injeção de dependência, como o Hilt, para substituir componentes no app no momento do teste. Consulte o Guia de teste do Hilt.

Próximas etapas

A página Estratégias de teste mostra como melhorar a produtividade usando diferentes tipos de testes.