Testar as atividades do app

As atividades atuam como contêineres para cada interação do usuário no seu app. Por isso, é importante testar o comportamento delas durante eventos no dispositivo, como:

  • Outro app, como o de telefone, interrompe a atividade.
  • A atividade é destruída e recriada pelo sistema.
  • O usuário move a atividade para um novo ambiente de janelas, como picture-in-picture (PIP) ou várias janelas.

É importante garantir o comportamento correto da atividade em resposta aos eventos descritos no ciclo de vida dela.

Este guia descreve como avaliar a capacidade do seu app de manter a integridade dos dados e uma boa experiência do usuário durante as transições de estado das atividades nos ciclos de vida.

Direcionar o estado de uma atividade

Um aspecto importante do teste das atividades do app envolve direcioná-las a estados específicos. Para definir essa etapa de "configuração" dos testes, use instâncias da classe ActivityScenario, que faz parte da biblioteca AndroidX Test. Ao usar essa classe, é possível colocar a atividade em estados que simulam eventos no nível do dispositivo.

ActivityScenario é uma API multiplataforma que pode ser usada em testes de unidade local e de integração no dispositivo. Em um dispositivo real ou virtual, a API ActivityScenario fornece segurança na linha de execução, sincronizando eventos entre as linhas de execução da instrumentação do teste e da atividade em teste.

Essa API é ideal para avaliar como uma atividade em teste se comporta quando é destruída ou criada. Esta seção apresenta os casos de uso mais comuns associados a essa API.

Criar uma atividade

Para criar a atividade em teste, adicione o código mostrado no snippet a seguir:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
       launchActivity<MyActivity>().use {
       }
    }
}

Depois de criá-la, ActivityScenario faz a transição da atividade para o estado RESUMED. Esse estado indica que a atividade está sendo executada e está visível para os usuários. Nele, você está livre para interagir com os elementos View da atividade usando testes de UI do Espresso.

O Google recomenda chamar close na atividade após a conclusão do teste. Isso limpa os recursos associados e melhora a estabilidade dos testes. ActivityScenario implementa Closeable, possibilitando aplicar a extensão use ou try-with-resources na linguagem de programação Java para que a atividade seja fechada logo a seguir.

Como alternativa, você pode usar ActivityScenarioRule para chamar ActivityScenario.launch automaticamente antes de cada teste e ActivityScenario.close na desmontagem do teste. O exemplo abaixo mostra como definir uma regra e ter uma instância de um cenário a partir dela:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @get:Rule var activityScenarioRule = activityScenarioRule<MyActivity>()

    @Test fun testEvent() {
        val scenario = activityScenarioRule.scenario
    }
}

Direcionar a atividade para um novo estado

Para direcionar a atividade para um estado diferente, como CREATED ou STARTED, chame moveToState(). Essa ação simula uma situação em que a atividade é parada ou pausada, respectivamente, porque é interrompida por outro app ou por uma ação do sistema.

Um exemplo de uso de moveToState() aparece no snippet de código a seguir.

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.moveToState(State.CREATED)
        }
    }
}

Determinar o estado atual da atividade

Para determinar o estado atual de uma atividade em teste, verifique o valor do campo state no seu objeto ActivityScenario. É útil verificar o estado de uma atividade em teste se ela redirecionar para outra atividade ou se encerrar sozinha, como demonstrado no snippet de código a seguir:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              startActivity(Intent(activity, MyOtherActivity::class.java))
            }

            val originalActivityState = scenario.state
        }
    }
}

Recriar a atividade

Quando um dispositivo está com poucos recursos, o sistema pode destruir uma atividade, exigindo que o app a recrie quando o usuário voltar para ele. Para simular essas condições, chame recreate():

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.recreate()
        }
    }
}

A classe ActivityScenario preserva o estado salvo da instância da atividade e quaisquer objetos anotados usando @NonConfigurationInstance. Esses objetos são carregados na nova instância da atividade em teste.

Recuperar resultados de atividades

Para verificar o código de resultado ou os dados associados a uma atividade concluída, verifique o valor do campo result no seu objeto ActivityScenario, conforme mostrado no snippet de código a seguir:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testResult() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.finish_button)).perform(click())

            // Activity under test is now finished.

            val resultCode = scenario.result.resultCode
            val resultData = scenario.result.resultData
        }
    }
}

Acionar ações na atividade

Todos os métodos em ActivityScenario são chamadas de bloqueio, então a API exige que sejam rodados na linha de execução de instrumentação.

Para acionar ações na atividade em teste, use os correspondentes de visualização do Espresso e interaja com os elementos na visualização:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use {
            onView(withId(R.id.refresh)).perform(click())
        }
    }
}

No entanto, caso seja necessário chamar um método na própria atividade, será possível fazê-lo de modo seguro implementando ActivityAction:

@RunWith(AndroidJUnit4::class)
class MyTestSuite {
    @Test fun testEvent() {
        launchActivity<MyActivity>().use { scenario ->
            scenario.onActivity { activity ->
              activity.handleSwipeToRefresh()
            }
        }
    }
}