Explorando a pirâmide de testes no Android — Testes instrumentados — parte 3

Phellipe Silva
Android Dev BR
Published in
7 min readAug 20, 2018

Na primeira e segunda partes dos posts sobre pirâmide de testes, tivemos uma visão geral dos conceitos básicos e de como está estruturada a base da pirâmide. Nesse post vamos explorar o centro dela, vamos falar especificamente dos testes instrumentados do seu projeto Android.

Por definição, testes instrumentados no Android são aqueles que rodam em aparelhos físicos ou em emuladores. Nesses testes o uso de mocks é reduzido e ganhamos a vantagem de utilizar a implementação real do framework do Android, assim possibilitando o uso de classes como SharedPreferences, Context, Parcelable e Intent. Uma característica dessa camada é que a execução dos testes começa a ficar mais lenta porém também atingimos um nível maior de confiabilidade e integração.

No centro da pirâmide, eu costumo separar os testes em 3 categorias (da base ao topo):

  • Testes integrados exclusivamente com AndroidJUnitRunner + AndroidJUnit4
  • Testes de UI com Espresso
  • Testes end-to-end com UI Automator
Foto de uma pirâmide de testes com destaque no centro

Testes com AndroidJUnitRunner + AndroidJUnit4

O AndroidJUnitRunner é um test runner que permite com que testes do JUnit sejam executados sobre aparelhos Android. Ele serve como base para todos os testes instrumentados e através dele podemos ter acesso a algumas classes específicas do framework Android (como a Context por exemplo).

Para ter acesso a API de instrumentação e poder construir testes nesse nível, o Android fornece a classe InstrumentationRegistry. Abaixo podemos ver um exemplo muito simples de como é a estrutura desse tipo de teste:

Exemplo de teste instrumentado com AndroidJUnitRunner. Retirado de: https://www.codevscolor.com/testing-in-android-part-4-instrumented-unit-test/

Percebam que por mais que estejamos utilizando um emulador ou smartphone para executar esse teste, ele ainda não é considerado um teste de UI. O teste a cima apenas salva um valor em um SharedPreferences e verifica que o valor foi salvo.

Testes que envolvam persistência de dados ou que acessem recursos reais do Android (como strings.xml), podem ser feitos na camada instrumentada da pirâmide.

Novidade (Editado 18/12/2019): Com o lançamento das bibliotecas de teste do AndroidX, agora temos uma nova forma de acessar os recursos do Android na camada instrumentada. Agora podemos utilizar o ApplicationProvider no lugar do InstrumentationRegistry como mostra o exemplo abaixo:

Exemplo de setup de uma classe de teste com ApplicationProvider em Kotlin.

Para ter mais informação sobre esse tipo de teste, acessar o link oficial do developers Android.

Testes com Espresso

Avançando para um nível superior na pirâmide, chegamos na parte de testes de UI com Espresso. O Espresso é uma ferramenta de automação de testes de UI feitas para pessoas desenvolvedoras que possuem mais familiaridade com a codebase. Diferentemente de algumas outras ferramentas de teste de UI, com Espresso você consegue inicializar uma tela diretamente sem passar por todo o fluxo até chegar nela.

A maneira de escrever um teste de Espresso é diferente de como escrevemos testes com Robolectric ou testes instrumentados com JUnit. Nele nós temos uma forma de escrever que se assemelha muito com o que um usuário faria na sua App. Basicamente selecionar uma view (onView), fazer alguma ação (perform) e verificar algo (check). Assim como o exemplo abaixo:

Exemplo de teste com Espresso. Retirado de: https://developer.android.com/training/testing/espresso/

O Espresso provê várias formas de buscar views na sua UI e de fazer assertivas sobre elas. Abaixo temos uma imagem com os principais comandos do Espresso:

Lista de comandos do Espresso. Retirado de: https://developer.android.com/training/testing/espresso/cheat-sheet

Também é interessante mencionar que com o Espresso nós também podemos testar WebViews, acessibilidade e Intents. Pessoalmente eu considero uma ferramenta obrigatória em sua pirâmide de testes Android.

Testes end-to-end com UI Automator

Chegando nessa camada da pirâmide, nos deparamos com os testes end-to-end, que são testes que têm como objetivo testar que um fluxo está se comportando de maneira adequada do início ao fim.

O UI Automator é uma ferramenta de testes end-to-end fornecida pela Google. A principal diferença entre o Espresso e o UI Automator é que o Espresso testa a UI em um escopo muito mais isolado e o UI Automator consegue testar em um escopo muito mais abrangente que vai além da aplicação que está sendo construída.

Com o UI Automator poderíamos escrever um teste que acesse as configurações do device pelo launcher, ou que entre em uma outro aplicativo. As possibilidade são muito maiores.

Uma outra vantagem do UI Automator é a possibilidade escrever um testes end-to-end utilizando o mesmo projeto no Android Studio. Algumas outras ferramentas de teste end-to-end não permitem isso fazendo que você tenha que ter um repositório separado para escrever seus testes.

Para facilitar a vida de quem escreve esse tipo de teste, foram criadas algumas ferramentas de suporte:

  • UI Automator Viewer: Que é uma ferramenta feita para inspecionar o layout da sua tela com mais detalhes e ajudar a extrair informações como IDs e content descriptions. Essa ferramenta esta localizada no caminho <android-sdk>/tools
  • Classe UiDevice: Classe no código que fornece funções auxiliares como mudar a orientação do device, apertar o botão back ou home e até abrir o menu de notificações.

Abaixo um trecho de código utilizando o UI Automator:

Exemplo de teste com UI Automator. Retirado de: https://developer.android.com/training/testing/ui-automator

Vale mencionar que os testes com UI Automator são os mais pesados e mais sensíveis a mudanças de sistema. Qualquer atualização do Google Play Services ou do próprio sistema operacional podem impactar seu testes. Como a própria documentação oficial mostra:

Cuidado: Recomendamos que você teste sua app usando o UI Automator somente quando precisar interagir com o sistema operacional para atender a um caso de uso crítico. Como o UI Automator interage com outros aplicativos e UI do sistema operacional, pode ser necessário corrigir seus testes após cada atualização do Android. Essas atualizações incluem atualizações de versão da plataforma e novas versões do Google Play Services. (Tradução livre)

Por isso devemos escolher muito bem os testes que queremos testar nessa ferramenta. Se podemos fazer o mesmo teste com Espresso, façamos com Espresso. 😄

Existe choque entre essas várias ferramentas de teste?

Como falado no post anterior, recentemente foi anunciado que o Robolectric fará parte das ferramentas de testes oficiais do Android. Agora que conhecemos mais algumas ferramentas de testes, podemos nos deparar com a seguinte dúvida:

Considerando que cada ferramenta de teste possui uma característica e forma de escrever específica… Não ficaria muito bagunçado ter tudo isso junto na mesma pirâmide? Como todas essas ferramentas se relacionam? Eu tenho que realmente aprender tudo isso?

É uma pergunta muito válida. Por um lado temos uma gama enorme de opções para escrever nossos testes, por outro lado pode nos faltar padronização se não tivermos cuidado. Por isso o Google quer unificar a maneira como escrevemos nossos testes em uma API única. Esse é um dos objetivos do projeto Nitrogen e do Test Support Library do Jetpack.

Por exemplo, tudo indica que a partir da versão 4.0 do Robolectric vamos ter a possibilidade de padronizar a escrita de testes para ficar muito semelhante ao que propõe o Espresso. Abaixo temos um exemplo de teste com Robolectric na nova versão 4.0:

Exemplo de teste instrumentado com Robolectric. Retirado de: http://robolectric.org/blog/2018/05/09/robolectric-4-0-alpha/

Percebe-se que é a mesma sintaxe do Espresso. Isso vai nos ajudar a diminuir a carga cognitiva ao construir nossos testes. Vale lembrar que essa padronização é algo que ainda está avançando no mundo Android e vamos ouvir mais a respeito em um futuro próximo.

Bônus: Testes intermitentes (Flaky tests)

É muito comum termos problemas com testes intermitentes a medida que nossa suíte de testes vai crescendo, especialmente nas partes mais altas da pirâmide. Os motivos podem ser vários:

  • Testes que guardam estado e que são repassados para os próximos testes.
  • Problemas de infraestrutura como baixa memória da máquina que está rodando o teste, especialmente quando se utiliza emuladores.
  • Animações que não são muito bem gerenciadas pelas ferramentas de teste.
  • Problemas de concorrência nas Threads
  • Entre outros….

O grande problema dos testes intermitentes é que eles tiram a confiabilidade da sua suite e atrapalham muito o processo de entrega contínua. Então vou mencionar algumas dicas que podem ajudar a mitigar esse problema no Android:

  • Utilizar o Android Test Orchestrator que cria uma sandbox para cada teste instrumentado fazendo com que seja muito difícil eles compartilharem estado. A desvantagem é que o Test Orchestrator faz com que sua suite de teste fique bem mais lenta.
  • Remover todas as animações do device que está sendo testado.
  • Fazer uso de IdlingResources, que são operadores que a pessoa que está construindo o teste pode implementar para avisar ao Espresso quando sua UI estará disponível para ser acessada.
  • Nos piores cenários podemos adicionar uma re-execução nos testes que falharam. Assim não necessitamos executar toda a suite de teste novamente, porém essa prática não é recomendada pois não resolve o problema na raiz.

E com isso terminamos a nossa terceira parte da série de posts sobre pirâmide de testes. Na próxima parte vamos explorar um pouco mais sobre os testes end-to-end que são construídos fora da codebase do Android e também vamos explorar um pouquinho dos testes manuais.

Feedbacks também são muito bem-vindos! Obrigado!

--

--

Phellipe Silva
Android Dev BR

Android engineer and test automation enthusiast. Working @Wise and formerly @ThoughtWorks. Twitter profile: @phellipeafsilva