Um recurso de inatividade representa uma operação assíncrona cujos resultados afetam as operações subsequentes em um teste de IU. Registrando 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, é válida apenas para operações que publicam mensagens na MessageQueue
, como uma subclasse de View
que esteja desenhando o conteúdo na tela.
Como o Espresso não reconhece outras operações assíncronas, incluindo aquelas executadas em uma linha de execução em segundo plano, ele não pode garantir a sincronização nessas situações. Para que o Espresso reconheça as operações longas do app, é preciso registrar cada uma delas como um recurso de inatividade.
Se você não usa recursos de inatividade ao testar os resultados do trabalho assíncrono do app, talvez seja necessário usar uma das seguintes soluções alternativas incorretas para melhorar a confiabilidade dos testes:
- Adicionar chamadas a
Thread.sleep()
. Quando você adiciona atrasos artificiais aos testes, o conjunto de testes demora mais para concluir a execução, e os testes ainda podem falhar algumas vezes quando executados em dispositivos mais lentos. Além disso, esses atrasos não são bem dimensionados, porque o app pode ter que executar um trabalho assíncrono mais demorado em uma versão futura. - Implementar wrappers de repetição, que usam um loop 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 repetições nos testes, cada nova execução consome recursos do sistema, principalmente a CPU.
- Usar 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. Os bloqueios também adicionam complexidade desnecessária ao código, dificultando a manutenção.
Com o Espresso, em vez de remover essas soluções alternativas não confiáveis dos testes, é possível registrar o trabalho assíncrono do app como recursos de inatividade.
Casos de uso comuns
Ao realizar operações semelhantes aos seguintes exemplos nos testes, use 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 por meio de um serviço do sistema ou de 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 IU que é posteriormente validada pelos seus testes.
Exemplo de implementação de recursos de inatividade
A lista a seguir descreve vários exemplos de implementação de recursos de inatividade que você pode integrar ao app:
CountingIdlingResource
- Mantém um contador de tarefas ativas. Quando o contador é zero, o recurso relacionado é 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 testes. 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. Esta classe usa umCountingIdlingResource
para manter o contador de tarefas ativas. IdlingScheduledThreadPoolExecutor
- Uma implementação personalizada de
ScheduledThreadPoolExecutor
. Ela oferece a mesma funcionalidade e recursos da classeIdlingThreadPoolExecutor
, mas que também pode monitorar tarefas agendadas para o futuro ou para serem executadas periodicamente.
Criar o próprio recurso de inatividade
Quando você usa recursos de inatividade nos testes do app, pode ser necessário oferecer gerenciamento ou registro personalizados de recursos. Nesses casos, as implementações listadas na seção anterior podem não ser suficientes. Se isso acontecer, estenda uma dessas implementações de recurso de inatividade ou crie o próprio recurso.
Se você implementar a própria funcionalidade de recurso de inatividade, lembre-se das práticas recomendadas a seguir, em especial a primeira:
- Invocar transições para o estado inativo fora das verificações de inatividade
- Depois que o app estiver inativo, chame
onTransitionToIdle()
fora de quaisquer implementações deisIdleNow()
. Dessa forma, o Espresso não fará uma segunda verificação desnecessária para determinar se um recurso de inatividade específico 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 terá efeito na primeira linha de cada teste. - Se você registrar um recurso de inatividade dentro de um teste, esse recurso terá efeito na próxima ação baseada no Espresso. Esse comportamento ocorre mesmo que a próxima ação esteja no mesmo teste que a instrução que registra o recurso de inatividade.
- Se você registrar um recurso de inatividade em um método anotado com
- Cancelar o registro de recursos de inatividade depois de usá-los
Para preservar recursos do sistema, cancele o registro de recursos de inatividade quando não precisar mais deles. Por exemplo, se você registrar um recurso de inatividade em um método anotado com
@Before
, é recomendado 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
Com esse contêiner para os recursos de inatividade do seu app, você pode registrar e cancelar o registro desses recursos várias vezes 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 disponibiliza uma classe de contêiner em que você pode colocar os recursos de inatividade do app. Essa classe, denominada IdlingRegistry
, é um artefato autossuficiente que introduz uma sobrecarga mínima no app. A classe também permite seguir estas etapas para melhorar a capacidade de manutenção do seu app:
- Crie uma referência ao
IdlingRegistry
, em vez dos recursos inativos que ele contém, nos testes do app. - Mantenha diferenças no conjunto de recursos de inatividade que você usa para cada variante de compilação.
- Defina os recursos de inatividade nos serviços do app, não nos componentes da IU que fazem referência a esses serviços.
Integrar recursos de inatividade ao app
Embora você possa adicionar recursos de inatividade a um app de maneiras diferentes, uma abordagem em particular mantém o encapsulamento do app, ao mesmo tempo em que permite definir uma operação específica representada por um determinado recurso de inatividade.
Abordagem recomendada
Ao adicionar recursos de inatividade ao app, é altamente recomendável realizar apenas as operações de registro e cancelamento de registro nos seus testes e colocar a lógica do próprio recurso de inatividade no código de produção do app.
Apesar de criar a situação incomum de usar uma interface somente teste no código de produção seguindo essa abordagem, é possível unir os recursos de inatividade em torno do código que você já tem, mantendo o tamanho do APK e a contagem de métodos do app.
Abordagens alternativas
Caso você prefira não colocar a lógica de recursos de inatividade no código de produção do app, existem várias outras estratégias de integração viáveis:
- Crie variantes de compilação, como as variações de produto do Gradle, e use recursos de inatividade apenas na compilação 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 própria injeção deve ter como origem 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 projeto pareça criar uma referência autossuficiente aos 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 a seguir.
Amostra (link em inglês)
- IdlingResourceSample: sincronização com jobs em segundo plano.