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 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:
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:
- 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.
- Exemplo: teste de captura de tela para um botão personalizado
- 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.
- Exemplo: testes de comportamento da interface que verificam o gerenciamento do estado em uma tela
- 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.
- Exemplo: jornadas ideais do usuário, testes de performance
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 |
Depurável |
Antes da fusão |
Recurso |
Nível do recurso Integração com componentes de outras equipes |
Simulado |
Local |
Depurável |
Pré-mesclagem |
Aplicativo |
Nível do aplicativo Integração com recursos e/ou serviços de outras equipes |
Servidor de |
Dispositivos |
Depurável |
Antes 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 |
Build de lançamento minimizado |
Pós-mesclagem |
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 |
|
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 |
|
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 |
|
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.