Recursos de inatividade do Espresso

Um recurso inativo representa uma operação assíncrona cujos resultados afetam operações subsequentes em um teste de interface. Ao registrar recursos de inatividade no Espresso, você pode validar essas operações assíncronas com mais confiabilidade ao testar seu app.

Identificar quando os recursos de inatividade são necessários

O Espresso oferece um conjunto sofisticado de recursos de sincronização. Essa característica do framework, no entanto, se aplica apenas a operações que publicam mensagens na MessageQueue, como uma subclasse de View que está desenhando o conteúdo na tela.

Como o Espresso não reconhece nenhuma outra operação assíncrona, incluindo aquelas em execução em uma linha de execução em segundo plano, ele não pode oferecer garantias de sincronização nessas situações. Para que o Espresso reconheça as operações de longa duração do app, é necessário registrar cada uma como recurso de inatividade.

Se você não usa recursos de inatividade ao testar os resultados do trabalho assíncrono do app, pode ser necessário usar uma das seguintes soluções alternativas inadequadas para melhorar a confiabilidade dos testes:

  • Adicionar chamadas a Thread.sleep(). Quando você adiciona atrasos artificiais aos testes, o conjunto de testes leva mais tempo para terminar a execução, e seus testes ainda podem falhar algumas vezes quando executados em dispositivos mais lentos. Além disso, esses atrasos não são bem dimensionados, já que seu app pode precisar executar um trabalho assíncrono mais demorado em uma versão futura.
  • Implementar wrappers de repetição,que usam uma repetição para verificar repetidamente se o app ainda está executando um trabalho assíncrono até que o tempo limite seja atingido. Mesmo que você especifique uma contagem máxima de tentativas nos testes, cada nova execução consome recursos do sistema, especialmente a CPU.
  • Uso de instâncias de CountDownLatch,que permitem que uma ou mais linhas de execução aguardem até que um número específico de operações executadas em outra linha de execução seja concluído. Esses objetos exigem que você especifique um tempo limite. Caso contrário, o app poderá ser bloqueado indefinidamente. As travas também adicionam complexidade desnecessária ao código, dificultando a manutenção.

O Espresso permite remover essas soluções alternativas não confiáveis dos testes e registrar o trabalho assíncrono do app como recursos de inatividade.

Casos de uso comuns

Ao executar operações semelhantes aos exemplos a seguir nos testes, considere usar um recurso de inatividade:

  • Carregar dados da Internet ou de uma fonte de dados local.
  • Estabelecer conexões com bancos de dados e callbacks.
  • Gerenciar serviços usando um serviço do sistema ou uma instância de IntentService.
  • Executar lógica de negócios complexa, como transformações de bitmap.

É especialmente importante registrar recursos de inatividade quando essas operações atualizam uma interface que é validada pelos testes.

Exemplo de implementação de recursos de inatividade

A lista a seguir descreve vários exemplos de implementações de recursos de inatividade que você pode integrar ao seu app:

CountingIdlingResource
Mantém um contador de tarefas ativas. Quando o contador é zero, o recurso associado é considerado inativo. Essa funcionalidade se parece muito com a de um Semaphore. Na maioria dos casos, essa implementação é suficiente para gerenciar o trabalho assíncrono do app durante o teste.
UriIdlingResource
Semelhante a CountingIdlingResource, mas o contador precisa ser zero por um período específico antes que o recurso seja considerado inativo. Esse período de espera considera solicitações de rede consecutivas, em que um app na linha de execução pode fazer uma nova solicitação imediatamente depois de receber uma resposta a uma solicitação anterior.
IdlingThreadPoolExecutor
Uma implementação personalizada de ThreadPoolExecutor que monitora o número total de tarefas em execução nos pools de linhas de execução criados. Essa classe usa um CountingIdlingResource para manter o contador de tarefas ativas
.
IdlingScheduledThreadPoolExecutor
Uma implementação personalizada de ScheduledThreadPoolExecutor. Ela fornece as mesmas funcionalidades e recursos da classe IdlingThreadPoolExecutor, mas também pode monitorar tarefas programadas para o futuro ou para execução periódica.

Criar o próprio recurso de inatividade

Ao usar recursos de inatividade nos testes do app, pode ser necessário fornecer gerenciamento ou geração de registros de recursos personalizados. Nesses casos, as implementações listadas na seção anterior podem não ser suficientes. Se esse for o caso, estenda uma dessas implementações de recurso de inatividade ou crie uma própria.

Se você implementar sua própria funcionalidade de recurso de inatividade, lembre-se das seguintes práticas recomendadas, especialmente a primeira:

Invocar transições para o estado inativo fora das verificações de inatividade
Depois que o app ficar inativo, chame onTransitionToIdle() fora das implementações de isIdleNow(). Dessa forma, o Espresso não fará uma segunda verificação desnecessária para determinar se um determinado recurso inativo está inativo.

O snippet de código a seguir ilustra essa recomendação:

Kotlin

fun isIdle() {
    // DON'T call callback.onTransitionToIdle() here!
}

fun backgroundWorkDone() {
    // Background work finished.
    callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

    // Don't do any post-processing work beyond this point. Espresso now
    // considers your app to be idle and moves on to the next test action.
}

Java

public void isIdle() {
    // DON'T call callback.onTransitionToIdle() here!
}

public void backgroundWorkDone() {
    // Background work finished.
    callback.onTransitionToIdle() // Good. Tells Espresso that the app is idle.

    // Don't do any post-processing work beyond this point. Espresso now
    // considers your app to be idle and moves on to the next test action.
}
Registrar recursos de inatividade antes que eles se tornem necessários

Os benefícios de sincronização associados aos recursos de inatividade só entram em vigor depois que o Espresso invoca o método isIdleNow() desse recurso pela primeira vez.

A lista a seguir mostra vários exemplos dessa propriedade:

  • Se você registrar um recurso de inatividade em um método anotado com @Before, esse recurso entrará em vigor na primeira linha de cada teste.
  • Se você registrar um recurso de inatividade em um teste, esse recurso entrará em vigor durante a próxima ação baseada no Espresso. Esse comportamento ainda ocorrerá mesmo que a próxima ação esteja no mesmo teste da instrução que registra o recurso de inatividade.
Cancelar o registro de recursos de inatividade depois de usá-los

Para economizar recursos do sistema, cancele o registro de recursos de inatividade assim que não precisar mais deles. Por exemplo, se você registrar um recurso de inatividade em um método anotado com @Before, é melhor cancelar o registro desse recurso em um método correspondente anotado com @After.

Usar um registro de inatividade para registrar e cancelar o registro de recursos de inatividade

Ao usar esse contêiner para os recursos de inatividade do seu app, é possível registrar e cancelar o registro desses recursos repetidamente, conforme necessário, e ainda observar um comportamento consistente.

Manter apenas o estado simples do app nos recursos de inatividade

Por exemplo, os recursos de inatividade que você implementa e registra não podem conter referências a objetos View.

Registrar recursos de inatividade

O Espresso oferece uma classe de contêiner em que é possível colocar os recursos de inatividade do app. Essa classe, chamada IdlingRegistry, é um artefato autônomo que introduz uma sobrecarga mínima no app. Ela também permite seguir estas etapas para melhorar a capacidade de manutenção do app:

  • Nos testes do app, crie uma referência ao IdlingRegistry, em vez dos recursos de inatividade que ele contém.
  • Mantenha diferenças na coleção de recursos de inatividade que você usa para cada variante de build.
  • Defina recursos de inatividade nos serviços do app, em vez de nos componentes da IU que se referem a esses serviços.

Integrar recursos de inatividade ao app

Embora você possa adicionar recursos de inatividade a um app de várias maneiras diferentes, uma abordagem em particular mantém o encapsulamento do app e, ao mesmo tempo, permite que você especifique uma operação específica representada por um determinado recurso de inatividade.

Ao adicionar recursos de inatividade ao app, é altamente recomendável colocar a lógica de recursos inativos no próprio app e realizar apenas as operações de registro e cancelamento de registro nos seus testes.

Embora você crie a situação incomum de usar uma interface somente teste no código de produção seguindo essa abordagem, é possível envolver recursos de inatividade em torno do código que você já tem, mantendo o tamanho do APK e a contagem de métodos do seu app.

Abordagens alternativas

Se você preferir não ter a lógica de recursos de inatividade no código de produção do app, há várias outras estratégias de integração viáveis:

  • Crie variantes de build, como as variações de produto do Gradle, e use recursos de inatividade somente no build de depuração do app.
  • Use um framework de injeção de dependência como o Dagger (em inglês) para injetar o gráfico de dependência de recursos de inatividade do app nos seus testes. Se você está usando o Dagger 2, a injeção em si precisa se originar de um subcomponente.
  • Implemente um recurso de inatividade nos testes do app e exponha a parte da implementação que precisa ser sincronizada nesses testes.

    Cuidado : embora essa decisão de design pareça criar uma referência independente a recursos de inatividade, ela também quebra o encapsulamento em todos os apps, exceto nos mais simples.

Outros recursos

Para saber mais sobre o uso do Espresso em testes do Android, consulte os recursos abaixo.

Exemplos