Usar cópias de teste no Android

Ao projetar a estratégia de teste para um elemento ou sistema, há três aspectos de teste relacionados:

  • Escopo: quanto do código é usado no teste? Os testes podem verificar de código aberto, o aplicativo inteiro ou algum meio intermediário. A escopo testado está em teste e geralmente se refere a ele como Sujeito em Teste, incluindo também o Sistema em teste ou a Unidade em teste.
  • Velocidade: qual é a velocidade de execução do teste? As velocidades de teste podem variar de milissegundos vários minutos.
  • Fidelidade: como o "mundo real" é o teste? Por exemplo, se parte do código que você está testando precisa fazer uma solicitação de rede. O código de teste realmente essa solicitação de rede ou ele imita o resultado? Se o teste realmente mostrar com a rede, ela terá maior fidelidade. A desvantagem é que pode levar mais tempo para ser executado, resultar em erros se a rede ficar inativa ou pode ter um alto custo de uso.

Veja o que testar para saber como começar a definir sua estratégia de teste.

Isolamento e dependências

Ao testar um elemento ou sistema de elementos, você faz isso isolados. Para exemplo, para testar um ViewModel, não é necessário iniciar um emulador nem iniciar uma interface. porque ela não depende (ou não deve) do framework do Android.

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

Quando você precisa fornecer uma dependência para um objeto em teste, uma prática comum é criar um teste duplo (ou objeto de teste). Cópias de teste são objetos que parecerem e atuarem como componentes do app, mas são criados no teste para que fornecem um comportamento ou dados específicos. As principais vantagens são que elas tornam suas os testes com mais rapidez e simplicidade.

Tipos de duplas de teste

Há vários tipos de testes duplos:

Falso Um teste duplo que está "funcionando" implementação da classe, mas ela é implementada de forma a ser boa para testes, mas inadequada para produção.

Exemplo: um banco de dados na memória.

As falsificações não precisam de uma estrutura fictícia e são leves. Eles são preferíveis.

Modelo Um duplo de teste que se comporta da mesma forma que você o programa para se comportar e que tem expectativas sobre suas interações. As simulações vão falhar nos testes se as interações não corresponderem aos requisitos que você definiu. Geralmente, as simulações são criadas com um framework de simulação para fazer tudo isso.

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

Stub Um duplo de teste que se comporta da forma como você o programa para se comportar, mas não tem expectativas sobre suas interações. Geralmente criado com uma estrutura simulada. Para simplificar, é melhor usar falsificações em vez de stubs.
Fictício Um teste duplo que é transmitido, mas não usado, por exemplo, se você só precisar 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 monitora algumas informações adicionais, semelhante aos mockups. Elas geralmente são evitadas por aumentar a complexidade. Portanto, é preferível usar falsificações ou simulações em vez de espiões.
Sombra Falso usado no Robolectric.

Exemplo de uso de simulação

Suponha que você queira fazer o teste de unidade de um ViewModel que depende de uma interface. chamado UserRepository e expõe o nome do primeiro usuário a uma interface. Você pode criar um teste duplo falso implementando a interface e retornando o valor dados.

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 dos dados locais e remotos. fontes que a versão de produção usaria. O arquivo reside na fonte de teste definido e não é enviado com o aplicativo de produção.

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

O teste a seguir verifica se o ViewModel expõe corretamente o primeiro usuário para a 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 O ViewModel é criado pelo testador. No entanto, pode ser difícil substituir elementos arbitrários em testes maiores.

Como substituir componentes e injeção de dependência

Quando os testes não têm controle sobre a criação dos sistemas testados, a substituição de componentes para cópias de teste fica mais complexa e exige arquitetura do seu app para seguir um design testável.

Mesmo grandes testes completos podem se beneficiar do uso de cópias de teste, como um teste de IU instrumentado que navega por um fluxo completo do usuário no app. Em Nesse caso, convém tornar o teste hermético. O teste hermético evita todas as dependências externas, como buscar dados da Internet. Isso melhora a confiabilidade e o desempenho.

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

É possível projetar seu aplicativo manualmente para alcançar essa flexibilidade, mas recomendamos usando 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.

Robolectric

No Android, você pode usar o framework Robolectric (link em inglês), que fornece um tipo especial de teste duplo. O Robolectric permite executar testes em na estação de trabalho ou no ambiente de integração contínua. Ele usa uma JVM normal, sem um emulador ou dispositivo. Ela simula a inflação de visualizações, o carregamento de recursos e outras partes do framework do Android com testes duplos chamada sombras.

O Robolectric é um simulador, portanto, não deve substituir testes de unidade simples nem ser usado para fazer testes de compatibilidade. Fornece velocidade e reduz os custos de menor fidelidade em alguns casos. Uma boa abordagem para testes de interface é torná-los compatível com testes Robolectric e instrumentados, e decidir quando executar dependendo da necessidade de testar a funcionalidade ou compatibilidade. Café expresso e do Compose podem ser executados no Robolectric.