Teste de estratégias

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

As equipes atingem níveis mais altos de produtividade quando usam uma abordagem sistemática para testes, combinada com melhorias de infraestrutura. Isso fornece feedback oportuno sobre como o código se comporta. Uma boa estratégia de teste faz o seguinte:

  • Detecta problemas o mais cedo possível.
  • Executa rapidamente.
  • Fornece indicações claras quando algo precisa ser corrigido.

Esta página ajudará você a decidir quais tipos de testes implementar, onde eles devem ser executados e com que frequência.

A pirâmide de testes

É possível categorizar os testes em aplicativos modernos por tamanho. 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, os testes grandes têm mais fidelidade* e podem descobrir muito mais problemas de uma 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 é visualizada em uma pirâmide.
Figura 1. A distribuição do número de testes por escopo geralmente é mostrada em uma pirâmide.

A maioria dos apps precisa 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 da localização de bugs.

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

Uma estratégia de alto impacto, em que muitos dos testes são realizados manualmente e os testes de dispositivos são executados apenas todas as noites.
Figura 2. Uma estratégia com muitos testes manuais e testes de dispositivo executados apenas à noite.

Isso significa que poucos testes são executados antes da mesclagem. Se houver um bug, os testes poderão não detectá-lo até que os testes completos noturnos ou semanais sejam executados.

É importante considerar as implicações que isso tem para o custo de identificação e correção de bugs e por que é importante direcionar seus esforços para testes menores e mais frequentes:

  • Quando o bug é detectado por um teste de unidade, ele geralmente é corrigido em minutos, portanto, o custo é baixo.
  • 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 uma versão. 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. Portanto, o ciclo de feedback é o mais longo e caro.

Uma estratégia de teste escalonável

Tradicionalmente, a pirâmide de testes é dividida em três categorias:

  • Testes de unidade
  • Testes de integração
  • Testes completos.

No entanto, esses conceitos não têm definições precisas. Por isso, as equipes podem querer 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 candidato à versão, em ordem crescente.
Figura 3. Uma pirâmide de teste de cinco camadas.
  • Um teste de unidade é executado na máquina host e verifica uma única unidade funcional de lógica sem dependências no framework do Android.
    • Exemplo: verificar erros de um em uma função matemática.
  • Um teste de componente verifica a funcionalidade ou aparência de um módulo ou componente de maneira independente de outros componentes do 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 interface para verificar mudanças de configuração em testes de acessibilidade e localização de um dobrável
  • O teste de candidato a lançamento verifica a funcionalidade de um build de lançamento. Eles são semelhantes aos testes de aplicativos, exceto que o binário do aplicativo é minificado e otimizado. Esses são grandes testes de integração de ponta a ponta 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 leva em conta a fidelidade, o tempo, o escopo e o nível de isolamento. É possível realizar diferentes tipos de testes em várias camadas. Por exemplo, a camada de teste do aplicativo pode conter testes de comportamento, capturas 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é-mesclagem

Componente

Nível do módulo ou componente

Várias classes juntas

Não

Local
Robolectric
Emulator

Depurável

Antes da fusão

Recurso

Nível do recurso

Integração com componentes de outras equipes

Simulado

Local
Robolectric
Emulator
Devices

Depurável

Pré-mesclagem

Aplicativo

Nível do aplicativo

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

Servidor de
estágio simulado
de produção

Dispositivos
emulador

Depurável

Antes da fusão
Depois da fusão

Candidata a lançamento

Nível do aplicativo

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

Servidor de produção

Dispositivos
emulador

Build de lançamento minimizado

Pós-mesclagem
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 desse recurso: a interface de um fluxo de login. Dependendo do que você precisa testar, escolha categorias diferentes:

Sujeito 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 inserido. Ele não tem dependências.

Testes de unidade

Teste de unidade JVM local

Comportamento da interface 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 em execução 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 para um gerenciador de autenticação e recebe respostas que podem conter diferentes erros.

Testes de recursos

Teste de JVM com falsificações

Caixa de diálogo de login

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

Testes de aplicativos

Teste de comportamento da interface em execução no Robolectric

Jornada ideal do usuário: fazer login

Um fluxo de login completo usando uma conta de teste em um servidor de preparação

Versão candidata a lançamento

Teste de comportamento da interface do Compose completo em execução no dispositivo

Em alguns casos, a classificação de algo em uma categoria ou outra pode ser subjetiva. Pode haver outros motivos para um teste ser movido para cima ou para baixo, como custo de infraestrutura, inconsistência e longos tempos de teste.

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 estratégia de testes. Normalmente, as equipes de controle de qualidade realizam testes de candidato à versão, mas também podem estar envolvidas em outras etapas. Por exemplo, testes exploratórios de bugs em um recurso sem um script.

Testar a infraestrutura

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

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

Categoria

Ambiente (onde)

Acionar (quando)

Unidade

[Local][4]

Cada confirmação

Componente

Local

Cada confirmação

Recurso

Emuladores e locais

Antes da mesclagem, antes de mesclar ou enviar uma mudança

Aplicativo

Local, emuladores, 1 smartphone, 1 dobrável

Após a mesclagem, depois de mesclar ou enviar uma mudança

Candidata a lançamento

8 smartphones diferentes, 1 dobrável e 1 tablet

Antes do lançamento

  • Os testes de unidade e de componente são executados no sistema de integração contínua para cada nova confirmação, mas apenas para os módulos afetados.
  • Todos os testes de unidade, componente e recurso são executados antes da mesclagem ou do envio de uma mudança.
  • Os testes de aplicação são executados após a mesclagem.
  • Os testes do Release Candidate são executados todas as noites em um smartphone, um dispositivo dobrável e um tablet.
  • Antes de um lançamento, os testes de candidato à 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 o tempo de build e de teste da CI, mas também prolongar o ciclo de feedback.