Teste de estratégias

Os testes automatizados ajudam a melhorar a qualidade do app de várias maneiras. Por exemplo, ela ajuda a realizar a validação, detectar regressões e verificar a compatibilidade. Uma boa estratégia de teste permite aproveitar os testes automatizados para se concentrar em um benefício importante: produtividade do desenvolvedor.

As equipes alcançam níveis mais altos de produtividade quando usam uma abordagem sistemática para testes combinada com melhorias na infraestrutura. Isso fornece feedback oportuno sobre o comportamento do código. Uma boa estratégia de teste faz o seguinte:

  • Detecta problemas o mais cedo possível.
  • Execução rápida.
  • Fornece indicações claras quando algo precisa ser corrigido.

Esta página vai ajudar você a decidir quais tipos de testes implementar, onde e com que frequência executá-los.

A pirâmide de testes

É possível categorizar testes em aplicativos modernos por tamanho. Os testes pequenos se concentram apenas em uma pequena parte do código, o que os torna rápidos e confiáveis. Testes grandes têm um escopo amplo e exigem configurações mais complexas que são difíceis de manter. No entanto, testes grandes têm mais fidelidade* e podem descobrir muito mais problemas de uma só vez.

Fidelidade se refere à semelhança do ambiente de execução de teste com o ambiente de produção.

A distribuição do número de testes por escopo geralmente é mostrada em uma pirâmide.
Figura 1. A distribuição do número de testes por escopo geralmente é visualizada em uma pirâmide.

A maioria dos apps deve ter muitos testes pequenos e relativamente poucos testes grandes. A distribuição de testes em cada categoria precisa formar uma pirâmide, com os testes pequenos mais numerosos formando a base e os testes grandes menos numerosos formando a ponta.

Minimizar o custo de um bug

Uma boa estratégia de teste maximiza a produtividade do desenvolvedor e minimiza o custo de encontrar bugs.

Considere um exemplo de uma estratégia possivelmente ineficiente. Aqui, o número de testes por tamanho não se organiza em uma pirâmide. Há muitos testes de ponta a ponta grandes e poucos testes de componentes da interface:

Uma estratégia desequilibrada em que muitos testes são realizados manualmente e os testes de dispositivo são executados apenas à noite.
Figura 2. Uma estratégia desequilibrada em que muitos testes são realizados manualmente e os testes de dispositivo são executados apenas à noite.

Isso significa que poucos testes são executados antes da fusão. Se houver um bug, os testes só vão detectá-lo quando os testes de ponta a ponta noturnos ou semanais forem executados.

É importante considerar as implicações disso para o custo de identificar e corrigir bugs e por que é importante direcionar seus esforços de teste para testes menores e mais frequentes:

  • Quando um teste de unidade detecta um bug, ele geralmente é corrigido em minutos, o que reduz o custo.
  • Um teste completo pode levar dias para descobrir o mesmo bug. Isso pode envolver vários membros da equipe, reduzindo a produtividade geral e potencialmente atrasando um lançamento. O custo desse bug é maior.

Dito isso, uma estratégia de teste ineficiente é melhor do que nenhuma estratégia. Quando um bug chega à produção, a correção leva muito tempo para chegar aos dispositivos do usuário, às vezes semanas. Assim, o ciclo de feedback é o mais longo e caro.

Uma estratégia de teste escalonável

A pirâmide de testes é tradicionalmente dividida em três categorias:

  • Testes de unidade
  • Testes de integração
  • Testes de ponta a ponta.

No entanto, esses conceitos não têm definições precisas. Por isso, as equipes podem definir as categorias de maneira diferente, por exemplo, usando cinco camadas:

Uma pirâmide de testes de cinco camadas com as categorias testes de unidade, testes de componentes, testes de recursos, testes de aplicativos e testes de candidatos a lançamento, em ordem crescente.
Figura 3. Uma pirâmide de testes de cinco camadas.
  • Um teste de unidade é executado na máquina host e verifica uma única unidade funcional de lógica sem dependências do framework Android.
    • Exemplo: verificar erros de um em uma função matemática.
  • Um teste de componente verifica a funcionalidade ou a aparência de um módulo ou componente independente de outros componentes no sistema. Ao contrário dos testes de unidade, a área de superfície de um teste de componente se estende a abstrações mais altas acima de métodos e classes individuais.
  • Um teste de recurso verifica a interação de dois ou mais componentes ou módulos independentes. Os testes de recursos são maiores e mais complexos e geralmente operam no nível do recurso.
  • Um teste de aplicativo verifica a funcionalidade de todo o aplicativo na forma de um binário implantável. São testes de integração grandes que usam um binário depurável, como um build de desenvolvimento que pode conter hooks de teste, como o sistema em teste.
    • Exemplo: teste de comportamento da UI para verificar mudanças de configuração em um dispositivo dobrável, testes de localização e acessibilidade
  • Um teste candidato a lançamento verifica a funcionalidade de um build de lançamento. Eles são semelhantes aos testes de aplicativos, mas o binário do aplicativo é minificado e otimizado. São testes de integração de ponta a ponta grandes que são executados em um ambiente o mais próximo possível da produção sem expor o app a contas de usuário ou back-ends públicos.

Essa categorização considera fidelidade, tempo, escopo e nível de isolamento. Você pode ter diferentes tipos de testes em várias camadas. Por exemplo, a camada de teste de aplicativo pode conter testes de comportamento, captura de tela e desempenho.

Escopo

Acesso à rede

Execução

Tipo de build

Ciclo de vida

Unidade

Método ou classe única com dependências mínimas.

Não

Local

Depurável

Pré-fusão

Componente

Nível do módulo ou do componente

Várias turmas juntas

Não

Local
Robolectric
Emulator

Depurável

Pré-fusão

Recurso

Nível do recurso

Integração com componentes de outras equipes

Mocked

Local
Robolectric
Emulator
Devices

Depurável

Pré-fusão

Aplicativo

Nível do aplicativo

Integração com recursos e/ou serviços de outras equipes

Mocked
Staging server
Prod server

Emulador
Dispositivos

Depurável

Pré-fusão
Pós-fusão

Versão candidata a lançamento

Nível do aplicativo

Integração com recursos e/ou serviços de outras equipes

Servidor de produção

Emulador
Dispositivos

Build de lançamento minificado

Pós-fusão
Pré-lançamento

Decidir a categoria do teste

Como regra geral, considere a camada mais baixa da pirâmide que pode dar à equipe o nível certo de feedback.

Por exemplo, considere como testar a implementação deste recurso: a interface de um fluxo de login. Dependendo do que você precisa testar, escolha categorias diferentes:

Objeto em teste

Descrição do que está sendo testado

Categoria do teste

Exemplo de tipo de teste

Lógica do validador de formulários

Uma classe que valida o endereço de e-mail em relação a uma expressão regular e verifica se o campo de senha foi preenchido. Não tem dependências.

Testes de unidade

Teste de unidade JVM local

Comportamento da interface do usuário do formulário de login

Um formulário com um botão que só é ativado quando o formulário é validado

Testes de componentes

Teste de comportamento da interface executado no Robolectric

Aparência da interface do formulário de login

Um formulário que segue uma especificação de UX

Testes de componentes

Teste de captura de tela da visualização do Compose

Integração com o gerenciador de autenticação

A interface que envia credenciais a um gerenciador de autenticação e recebe respostas que podem conter erros diferentes.

Testes de recursos

Teste de JVM com simulações

Caixa de diálogo de login

Uma tela mostrando o formulário de login quando o botão de login é pressionado.

Testes de apps

Teste de comportamento da interface executado no Robolectric

Jornada ideal do usuário: fazer login

Um fluxo de login completo usando uma conta de teste em um servidor de staging

Versão candidata a lançamento

Teste de comportamento da interface do Compose completo executado no dispositivo

Em alguns casos, a classificação de um conteúdo em uma categoria ou outra pode ser subjetiva. Há outros motivos para um teste ser movido para cima ou para baixo, como custo de infraestrutura, instabilidade e tempos de teste longos.

A categoria de teste não determina o tipo de teste, e nem todos os recursos precisam ser testados em todas as categorias.

Os testes manuais também podem fazer parte da sua estratégia de teste. Normalmente, as equipes de controle de qualidade realizam testes de versão candidata, mas também podem estar envolvidas em outras etapas. Por exemplo, testes exploratórios para bugs em um recurso sem um script.

Infraestrutura de teste

Uma estratégia de teste precisa ser apoiada por infraestrutura e ferramentas para ajudar os desenvolvedores a executar testes continuamente e aplicar regras que garantam que todos os testes sejam aprovados.

É possível categorizar os testes por escopo para definir quando e onde executar cada um. Por exemplo, seguindo o modelo de cinco camadas:

Categoria

Ambiente (onde)

Gatilho (quando)

Unidade

[Local][4]

Cada commit

Componente

Local

Cada commit

Recurso

Local e emuladores

Antes da mesclagem ou do envio de uma mudança

Aplicativo

Local, emuladores, 1 smartphone, 1 dobrável

Após a fusão, depois de mesclar ou enviar uma mudança

Versão candidata a lançamento

8 smartphones diferentes, 1 dobrável, 1 tablet

Antes do lançamento

  • Os testes de unidade e componente são executados no sistema de integração contínua para cada novo commit, mas apenas para os módulos afetados.
  • Todos os testes de unidade, componente e recurso são executados antes de mesclar ou enviar uma mudança.
  • Os testes de aplicativo são executados após a fusão.
  • Os testes Release Candidate são executados todas as noites em smartphones, dispositivos dobráveis e tablets.
  • Antes de um lançamento, os testes de candidato a lançamento são executados em um grande número de dispositivos.

Essas regras podem mudar com o tempo quando o número de testes afeta a produtividade. Por exemplo, se você mover os testes para uma cadência noturna, poderá diminuir os tempos de build e teste de CI, mas também poderá prolongar o ciclo de feedback.