Conceitos básicos de testes

Os usuários interagem com seu app em vários níveis, desde o uso de botões até o download de informações no dispositivo. Por isso, teste diversos casos de uso e interações à medida que desenvolve seu app de maneira iterativa.

Organizar o código para teste

À medida que o app cresce, pode ser necessário buscar dados em um servidor, interagir com os sensores do dispositivo, acessar o armazenamento local ou renderizar interfaces de usuário complexas. A versatilidade do app exige uma estratégia de teste abrangente.

Criar e testar o código de maneira iterativa

Ao desenvolver um recurso iterativamente, comece programando um novo teste ou adicionando casos e declarações a um teste de unidade existente. O teste falha no início, porque o recurso ainda não foi implementado.

É importante considerar as unidades de responsabilidade que surgem ao criar o novo recurso. Para cada unidade, programe um teste de unidade correspondente. Seus testes de unidade precisam esgotar quase todas as interações possíveis com a unidade, incluindo interações padrão, entradas inválidas e casos em que os recursos não estão disponíveis. Use as bibliotecas do Jetpack sempre que possível. Com essas bibliotecas bem testadas, você pode se concentrar em validar o comportamento específico do app.

O ciclo de desenvolvimento dos testes consiste em programar um teste de unidade com falha, programar o código para transmissão e depois refatorar. Todo o ciclo de desenvolvimento de recursos existe dentro de uma etapa de um ciclo maior baseado em IU.
Figura 1. Os dois ciclos associados ao desenvolvimento iterativo voltado para testes.

O fluxo de trabalho completo, como mostrado na Figura 1, contém uma série de ciclos iterativos aninhados, em que um ciclo longo, lento e controlado pela IU testa a integração de unidades de código. Teste as próprias unidades usando ciclos de desenvolvimento mais curtos e rápidos. Esse conjunto de ciclos continua até que o app satisfaça a todos os casos de uso.

Ver o app como uma série de módulos

Para testar o código com mais facilidade, desenvolva-o em relação a módulos, em que cada módulo representa uma tarefa específica que o usuário conclui no app. Essa perspectiva contrasta a visualização baseada em pilha de um app que normalmente contém camadas representando a IU, a lógica de negócios e os dados.

Por exemplo, um app de "lista de tarefas" pode ter módulos para criar tarefas, exibir estatísticas sobre tarefas concluídas e tirar fotos a serem associadas a uma tarefa específica. Essa arquitetura modular também ajuda a manter as classes não relacionadas separadas e oferece uma estrutura natural para atribuir propriedade à sua equipe de desenvolvimento.

É importante definir limites claros em torno de cada módulo e criar novos módulos à medida que seu app cresce em escala e complexidade. Cada módulo deve ter apenas uma área de foco, e as APIs que permitem a comunicação entre módulos devem ser consistentes. Para facilitar e agilizar os testes dessas interações entre módulos, crie implementações falsas dos seus módulos. Nos seus testes, a implementação real de um módulo pode chamar a implementação falsa do outro módulo.

Mas, ao criar um novo módulo, não se preocupe tanto em torná-lo completo de imediato. Um módulo específico pode não ter uma ou mais camadas da pilha do app sem que isso cause qualquer problema.

Para saber mais sobre como definir módulos no app e sobre a compatibilidade da plataforma para criação e publicação de módulos, consulte Android App Bundles.

Configurar o ambiente de teste

Ao configurar seu ambiente e as dependências para criar testes no app, siga as práticas recomendadas descritas nesta seção.

Organizar diretórios de teste baseados no ambiente de execução

Um projeto comum no Android Studio contém dois diretórios que recebem os testes. Organize seus testes da seguinte maneira:

  • O diretório androidTest precisa conter os testes executados em dispositivos reais ou virtuais. Isso inclui testes de integração, testes de ponta a ponta e outros em que a JVM sozinha não pode validar a funcionalidade do app.
  • O diretório test precisa conter os testes executados na máquina local, por exemplo, testes de unidade.

Analisar as vantagens e desvantagens da execução de testes em diferentes tipos de dispositivos

Ao executar os testes em um dispositivo, você pode escolher entre os seguintes tipos:

  • Dispositivo real
  • Dispositivo virtual (como o Emulator no Android Studio)
  • Dispositivo simulado (como o Robolectric)

Dispositivos reais oferecem a maior fidelidade, mas também demoram mais para executar os testes. Por outro lado, os dispositivos simulados são bem mais rápidos, mas oferecem uma fidelidade menor. No entanto, as melhorias da plataforma em recursos binários e loopers realistas permitem que dispositivos simulados produzam resultados mais fidedignos.

Os dispositivos virtuais oferecem um equilíbrio entre fidelidade e velocidade. Ao optar por esses dispositivos, use snapshots para minimizar o tempo de configuração entre os testes.

Considerar o uso de cópias de teste

Ao criar testes, é possível criar objetos reais ou cópias de teste, como objetos falsos ou simulados. Geralmente, o uso de objetos reais é melhor do que o uso de cópias de teste, especialmente quando o objeto em teste atende a uma das seguintes condições:

  • O objeto é um objeto de dados.
  • O objeto não funciona, a menos que se comunique com a versão do objeto real de uma dependência. Um bom exemplo é um gerenciador de callback de eventos.
  • Há dificuldade para replicar a comunicação do objeto com uma dependência. Um bom exemplo dessa situação é um gerenciador de banco de dados SQL, em que um banco de dados na memória oferece testes mais robustos do que falsificações dos resultados do banco de dados.

Em particular, as instâncias de simulação de tipos que você não possui geralmente causam testes instáveis que só funcionam depois que você compreende as complexidades da implementação desse tipo realizada por outra pessoa. Use essas simulações apenas como último recurso. Não há problema em simular os próprios objetos, mas lembre-se de que simulações anotadas com @Spy oferecem mais fidelidade do que as simulações que criam stubs de toda a funcionalidade em uma classe.

No entanto, se os testes tentarem realizar os seguintes tipos de operação em um objeto real, o mais recomendado será criar objetos falsos ou inclusive simulados:

  • Operações longas, como o processamento de um arquivo grande
  • Ações não herméticas, como conectar-se a uma porta aberta arbitrária
  • Configurações difíceis de criar

Dica: verifique com os autores da biblioteca se eles disponibilizam alguma infraestrutura de teste oficialmente compatível, como falsificações, em que você pode confiar com segurança.

Programar testes

Depois de configurar o ambiente de teste, é hora de programar testes que avaliem a funcionalidade do app. Esta seção descreve como programar testes pequenos, médios e grandes.

Níveis da pirâmide de testes

Uma pirâmide com três camadas
Figura 2. Pirâmide de testes, mostrando as três categorias que você precisa incluir no conjunto de testes do app.

A pirâmide de testes, mostrada na Figura 2, ilustra como o app precisa incluir as três categorias de teste: pequeno, médio e grande:

  • Testes pequenos são testes de unidade que validam o comportamento do app, uma classe por vez.
  • Testes médios são testes de integração que validam interações entre níveis da pilha dentro de um módulo ou interações entre módulos relacionados.
  • Testes grandes são testes de ponta a ponta que validam as jornadas do usuário, abrangendo vários módulos do app.

À medida que você sobe na pirâmide, dos testes pequenos até os testes grandes, a fidelidade de cada teste aumenta, mas o tempo de execução e o esforço para manutenção e depuração também se tornam maiores. Portanto, programe mais testes de unidade que testes de integração, e mais testes de integração que testes de ponta a ponta. Embora a proporção de testes para cada categoria possa variar de acordo com os casos de uso do app, geralmente recomendamos a seguinte divisão entre as categorias: 70% pequenos, 20% médios e 10% grandes.

Para saber mais sobre a pirâmide de testes do Android, consulte o vídeo da sessão Desenvolvimento voltado para testes no Android (em inglês) do Google I/O 2017, a partir de 1:51.

Programar testes pequenos

Os testes pequenos que você programa precisam ser testes de unidade altamente focados que validam exaustivamente a funcionalidade e os contratos de cada classe no app.

À medida que você adiciona e muda métodos em uma classe específica, crie e execute testes de unidade para eles. Se esses testes dependerem do framework do Android, use uma API unificada e independente de dispositivo, como APIs androidx.test. Essa consistência permite executar seu teste localmente, sem um dispositivo físico ou emulador.

Se seus testes dependem de recursos, ative a opção includeAndroidResources no arquivo build.gradle do app. Os testes de unidade poderão acessar versões compiladas dos seus recursos, permitindo que os testes sejam executados com mais rapidez e precisão.

app/build.gradle

    android {
        // ...

        testOptions {
            unitTests {
                includeAndroidResources = true
            }
        }
    }
    

Testes de unidade locais

Use as APIs do AndroidX Test sempre que possível para que os testes de unidade possam ser executados em um dispositivo ou emulador. Para testes que são sempre executados em uma máquina de desenvolvimento com JVM, é possível usar o Robolectric (em inglês).

O Robolectric simula o ambiente de execução do Android 4.1 (API nível 16) ou posterior e oferece falsificações mantidas pela comunidade, denominadas sombras. Essa funcionalidade permite testar o código que depende do framework, sem a necessidade de usar um emulador ou objetos simulados. O Robolectric é compatível com os seguintes aspectos da plataforma Android:

  • Ciclos de vida dos componentes
  • Loops de eventos
  • Todos os recursos

Testes de unidade de instrumentação

É possível executar testes de unidade de instrumentação em um dispositivo físico ou emulador. No entanto, essa forma de teste envolve tempos de execução significativamente mais lentos em comparação aos testes de unidade locais. Portanto, é melhor recorrer a essa abordagem apenas quando ela for indispensável para avaliar o comportamento do app em relação ao hardware real do dispositivo.

Ao executar testes de instrumentação, o AndroidX Test usa estas linhas de execução:

  • Linha de execução principal, conhecida também como "linha de execução de IU" ou "linha de execução de atividade", onde ocorrem interações da IU e eventos do ciclo de vida da atividade.
  • Linha de execução de instrumentação, onde a maioria dos testes é executada. Quando o conjunto de testes começa, a classe AndroidJUnitTest inicia essa linha de execução.

Se você precisar que um teste seja executado na linha de execução principal, anote-o com @UiThreadTest.

Programar testes médios

Além de testar cada unidade do app executando testes pequenos, valide o comportamento dele no nível do módulo. Para fazer isso, programe testes médios, que são testes de integração que validam a colaboração e a interação de um grupo de unidades.

Use a estrutura do app e os seguintes exemplos de teste médios (em ordem crescente de escopo) para definir a melhor maneira de representar grupos de unidades no app:

  1. Interações entre uma visualização e um modelo de visualização, por exemplo, testar um objeto Fragment, validar XML de layout ou avaliar a lógica de vinculação de dados de um objeto ViewModel.
  2. Testes na camada de repositório do app, que verificam se as diferentes fontes de dados e objetos de acesso a dados (DAOs, na sigla em inglês) interagem conforme esperado.
  3. Slices verticais do app, que testam interações em uma tela específica. Esse tipo de teste verifica as interações em todas as camadas da pilha do app.
  4. Testes de vários fragmentos que avaliam uma área específica do app. Diferente dos outros tipos de testes médios mencionados nesta lista, esse tipo de teste geralmente exige um dispositivo real, porque a interação em teste envolve vários elementos de IU.

Para realizar esses testes, faça o seguinte:

  1. Use métodos da biblioteca Espresso Intents. Para simplificar as informações transmitidas nesses testes, use falsificações e stubs.
  2. Combine o uso de IntentSubject e declarações do Truth para verificar os intents capturados.

Usar o Espresso ao executar testes médios de instrumentação

O Espresso ajuda a manter as tarefas sincronizadas enquanto você realiza interações de IU semelhantes às seguintes em um dispositivo ou no Robolectric:

  • Realizar ações em objetos View.
  • Avaliar como os usuários com necessidades de acessibilidade podem usar o app.
  • Localizar e ativar itens em objetos RecyclerView e AdapterView.
  • Validar o estado de intents de saída.
  • Verificar a estrutura de um DOM em objetos WebView.

Para saber mais sobre essas interações e como usá-las nos testes do app, consulte o guia do Espresso.

Programar testes grandes

Embora seja importante testar cada classe e módulo no app isoladamente, é igualmente importante validar fluxos de trabalho de ponta a ponta que orientam os usuários com relação a vários módulos e recursos. Esses tipos de testes formam gargalos inevitáveis no código, mas você pode minimizar esse efeito validando um app que seja o mais próximo possível do produto real concluído.

Se o app for pequeno o suficiente, você precisará de apenas um conjunto de testes grandes para avaliar a funcionalidade do app como um todo. Caso contrário, divida seus conjuntos de testes grandes por propriedade da equipe, verticais funcionais ou objetivos do usuário.

Normalmente, é melhor testar o app em um dispositivo emulado ou em um serviço baseado em nuvem (como o Firebase Test Lab) em vez de em um dispositivo físico porque você pode testar várias combinações de tamanhos de tela e configurações de hardware com mais facilidade e rapidez.

Compatibilidade de sincronização no Espresso

Além da compatibilidade com testes de instrumentação médios, o Espresso é compatível com sincronização ao concluir as seguintes tarefas em testes grandes:

  • Concluir fluxos de trabalho que ultrapassam os limites de processo do app. Disponível apenas no Android 8.0 (API nível 26) e posterior.
  • Monitorar operações longas em segundo plano no app.
  • Executar testes fora do dispositivo.

Para saber mais sobre essas interações e como usá-las nos testes do app, consulte o guia do Espresso.

Concluir outras tarefas de teste usando o AndroidX Test

Esta seção descreve como usar os elementos do AndroidX Test para refinar ainda mais os testes do app.

Criar declarações mais legíveis usando o Truth

A equipe do Guava oferece uma biblioteca de declarações fluentes denominada Truth (em inglês). Você pode usar essa biblioteca como uma alternativa às declarações do JUnit ou do Hamcrest ao criar a etapa de validação (ou etapa then) dos testes.

Em geral, use o Truth para expressar que um determinado objeto tem uma propriedade específica por meio de frases que contêm as condições que você está testando, por exemplo:

  • assertThat(object).hasFlags(FLAGS)
  • assertThat(object).doesNotHaveFlags(FLAGS)
  • assertThat(intent).hasData(URI)
  • assertThat(extras).string(string_key).equals(EXPECTED)

O AndroidX Test é compatível com vários outros assuntos para Android, a fim de facilitar ainda mais a criação de declarações do Truth:

A API do AndroidX Test ajuda você a realizar tarefas comuns relacionadas a testes de apps para dispositivos móveis. Veja mais informações na seção a seguir.

Criar testes de IU

O Espresso permite localizar e interagir de forma programática com os elementos de IU no app, mantendo a segurança da linha de execução. Para saber mais, consulte o guia do Espresso.

Executar testes de IU

A classe AndroidJUnitRunner define um executor de testes JUnit baseado em instrumentação que permite executar classes de teste no estilo JUnit 3 ou JUnit 4 em dispositivos Android. O executor facilita o carregamento do pacote de testes e do app em questão em um dispositivo ou emulador, executando seus testes e relatando os resultados.

Para aumentar ainda mais a confiabilidade desses testes, use o Android Test Orchestrator, que executa cada teste de IU na própria sandbox Instrumentation. Essa arquitetura reduz o estado compartilhado entre os testes e isola as falhas do app por teste. Para saber mais sobre as vantagens que o Android Test Orchestrator oferece ao testar seu app, consulte o guia do Android Test Orchestrator.

Interagir com elementos visíveis

A API do UI Automator permite interagir com elementos visíveis em um dispositivo, independentemente da atividade ou fragmento em foco.

Cuidado: teste o app com o UI Automator somente quando o app precisa interagir com a IU do sistema ou com outro app para atender a um caso de uso essencial. Como o UI Automator interage com uma IU específica do sistema, é preciso executar novamente e corrigir os testes do UI Automator depois de cada atualização de versão da plataforma e depois de cada nova versão do Google Play Services.

Como alternativa ao uso do UI Automator, recomendamos que você adicione testes herméticos ou separe seus testes grandes em um conjunto de testes pequenos e médios. Em particular, concentre-se em testar uma parte da comunicação entre apps por vez, por exemplo, enviando informações para outros apps e respondendo aos resultados de intent. A ferramenta Espresso-Intents pode ajudar na programação desses testes menores.

Adicionar verificações de acessibilidade para validar a usabilidade geral

A interface do app precisa permitir que todos os usuários, incluindo aqueles com necessidades de acessibilidade, interajam com o dispositivo e concluam tarefas mais facilmente no app.

Para ajudar a validar a acessibilidade do app, a biblioteca de testes do Android oferece várias partes da funcionalidade integrada. Veja mais informações nas seções a seguir. Para saber mais sobre como validar a usabilidade do app para diferentes tipos de usuário, consulte o guia sobre como testar a acessibilidade do app.

Robolectric

Ative as verificações de acessibilidade incluindo a anotação @AccessibilityChecks no início do conjunto de testes, conforme mostrado neste snippet de código:

Kotlin

    import org.robolectric.annotation.AccessibilityChecks

    @AccessibilityChecks
    class MyTestSuite {
        // Your tests here.
    }
    

Java

    import org.robolectric.annotation.AccessibilityChecks;

    @AccessibilityChecks
    public class MyTestSuite {
        // Your tests here.
    }
    

Espresso

Ative as verificações de acessibilidade chamando AccessibilityChecks.enable() no método setUp() do conjunto de testes, conforme mostrado neste snippet de código.

Para saber mais sobre como interpretar os resultados dessas verificações de acessibilidade, consulte o guia sobre verificação de acessibilidade do Espresso.

Kotlin

    import androidx.test.espresso.accessibility.AccessibilityChecks

    @Before
    fun setUp() {
        AccessibilityChecks.enable()
    }
    

Java

    import androidx.test.espresso.accessibility.AccessibilityChecks;

    @Before
    public void setUp() {
        AccessibilityChecks.enable();
    }
    

Impulsionar ciclos de vida de atividades e fragmentos

Use as classes ActivityScenario e FragmentScenario para testar como as atividades e os fragmentos do app respondem a interrupções no sistema e a mudanças na configuração. Para saber mais, consulte os guias sobre testes de atividades e testes de fragmentos.

Gerenciar ciclos de vida de serviços

O AndroidX Test inclui código para gerenciar os ciclos de vida dos principais serviços. Para saber como definir essas regras, consulte o guia Regras do JUnit4.

Avaliar todas as variantes de comportamento que mudam de acordo com a versão do SDK

Se o comportamento do app depende da versão do SDK do dispositivo, use a anotação @SdkSuppress transmitindo valores para minSdkVersion ou maxSdkVersion, dependendo de como você definiu as ramificações da lógica do app:

Kotlin

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    fun testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    fun testButtonClickOnPieAndHigher() {
        // ...
    }
    

Java

    @Test
    @SdkSuppress(maxSdkVersion = 27)
    public void testButtonClickOnOreoAndLower() {
        // ...
    }

    @Test
    @SdkSuppress(minSdkVersion = 28)
    public void testButtonClickOnPieAndHigher() {
        // ...
    }
    

Outros recursos

Para saber mais sobre testes no Android, consulte os recursos a seguir.

Exemplos

Codelabs