Testar as interações do usuário em um único app ajuda a garantir que os usuários não encontrem resultados inesperados ou tenham uma experiência ruim ao interagir com o app. É necessário adquirir o hábito de criar testes de interface do usuário (IU) caso precise verificar se a IU do app está funcionando corretamente.
O framework de testes do Espresso, oferecido pelo AndroidX Test, fornece APIs para criar testes de IU que simulem interações do usuário em um app de destino. Os testes do Espresso podem ser executados em dispositivos com Android 2.3.3 (API de nível 10) e versões mais recentes. Um dos principais benefícios do uso do Espresso é que ele oferece sincronização automática das ações de teste com a IU do app que você está testando.
O Espresso detecta quando a linha de execução principal está inativa. Assim, é possível executar seus comandos de teste no momento adequado, melhorando a confiabilidade dos testes. Esse recurso também dispensa a necessidade de adicionar soluções alternativas de tempo, como Thread.sleep()
no código de teste.
O framework de testes Espresso é uma API baseada em instrumentação e funciona com o executor de testes AndroidJUnitRunner
.
Configurar o Espresso
Antes de criar seu teste de IU com o Espresso, defina uma referência de dependência para a biblioteca do Espresso:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' }
Desative as animações no dispositivo de teste. Se as animações do sistema estiverem ativadas, poderão ocorrer resultados inesperados ou a falha do teste. Para desativar as animações, acesse Config., abra as Opções do desenvolvedor e desative todos os itens a seguir:
- Escala de animação da janela
- Escala de animação da transição
- Escala de duração do Animator
Caso queira configurar o projeto para usar os recursos do Espresso, além daqueles oferecidos pela API principal, consulte os guias específicos do Espresso.
Criar uma classe de teste do Espresso
Para criar um teste do Espresso, siga este modelo de programação:
-
Localize o componente de IU que você quer testar em uma
Activity
(por exemplo, um botão de login no app) chamando o métodoonView()
ou o métodoonData()
para controlesAdapterView
. -
Para simular uma interação específica do usuário nesse componente de IU, chame o método
ViewInteraction.perform()
ouDataInteraction.perform()
transmitindo a ação do usuário (por exemplo, um clique no botão de login). Para sequenciar várias ações no mesmo componente de IU, encadeie-as usando uma lista separada por vírgulas no argumento do método. - Repita as etapas acima conforme necessário para simular um fluxo de usuários em várias atividades no app de destino.
-
Use os métodos
ViewAssertions
para verificar se a IU reflete o estado ou o comportamento esperado quando essas interações são realizadas.
Essas etapas são abordadas com mais detalhes nas seções abaixo.
O snippet de código a seguir mostra como sua classe de teste pode invocar esse fluxo de trabalho básico:
Kotlin
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher .perform(click()) // click() is a ViewAction .check(matches(isDisplayed())) // matches(isDisplayed()) is a ViewAssertion
Java
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher .perform(click()) // click() is a ViewAction .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
Usar o Espresso com ActivityTestRule
A seção a seguir descreve como criar um novo teste do Espresso no estilo JUnit 4 e usar ActivityTestRule
para reduzir a quantidade de código de texto clichê necessário. Ao usar ActivityTestRule
, o framework de teste inicia a atividade em teste antes de cada método anotado com @Test
e antes de métodos anotados com @Before
. O framework lida com o encerramento da atividade após o teste ser concluído e todos os métodos anotados com @After
serem executados.
Kotlin
package com.example.android.testing.espresso.BasicSample import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import androidx.test.rule.ActivityTestRule import androidx.test.runner.AndroidJUnit4 @RunWith(AndroidJUnit4::class) @LargeTest class ChangeTextBehaviorTest { private lateinit var stringToBetyped: String @get:Rule var activityRule: ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java) @Before fun initValidString() { // Specify a valid string. stringToBetyped = "Espresso" } @Test fun changeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(stringToBetyped), closeSoftKeyboard()) onView(withId(R.id.changeTextBt)).perform(click()) // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(stringToBetyped))) } }
Java
package com.example.android.testing.espresso.BasicSample; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; @RunWith(AndroidJUnit4.class) @LargeTest public class ChangeTextBehaviorTest { private String stringToBetyped; @Rule public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class); @Before public void initValidString() { // Specify a valid string. stringToBetyped = "Espresso"; } @Test public void changeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(stringToBetyped), closeSoftKeyboard()); onView(withId(R.id.changeTextBt)).perform(click()); // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(stringToBetyped))); } }
Acessar componentes de IU
Antes que o Espresso possa interagir com o app em teste, é necessário especificar o componente ou a visualização de IU. O Espresso é compatível com o uso de matchers do Hamcrest (link em inglês) para especificar visualizações e adaptadores no app.
Localize a visualização, chamando o método onView()
e transmitindo um matcher que especifique a visualização que você quer usar. Esse processo é descrito em mais detalhes em Especificar um matcher de visualização.
O método onView()
retorna um objeto ViewInteraction
, que permite que o teste interaja com a visualização.
No entanto, chamar o método onView()
pode não funcionar se você quiser localizar uma visualização em um layout RecyclerView
.
Nesse caso, siga as instruções em Localizar uma visualização em uma AdapterView.
Observação: o método onView()
não verifica se a visualização especificada é válida. Em vez disso, o Espresso pesquisa apenas a hierarquia de visualizações atual por meio do matcher informado.
Se nenhuma correspondência for encontrada, o método gerará uma NoMatchingViewException
.
O snippet de código a seguir mostra como criar um teste que acessa um campo EditText
, insere uma string de texto, fecha o teclado virtual e executa um clique de botão.
Kotlin
fun testChangeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()) onView(withId(R.id.changeTextButton)).perform(click()) // Check that the text was changed. ... }
Java
public void testChangeText_sameActivity() { // Type text and then press the button. onView(withId(R.id.editTextUserInput)) .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); onView(withId(R.id.changeTextButton)).perform(click()); // Check that the text was changed. ... }
Especificar um matcher de visualização
Especifique um matcher de visualização usando estas abordagens:
- Chamar métodos na classe
ViewMatchers
. Por exemplo, para localizar uma visualização procurando uma string de texto que ela exibe, você pode chamar um método como este:Kotlin
onView(withText("Sign-in"))
Java
onView(withText("Sign-in"));
Da mesma forma, você pode chamar
withId()
e fornecer o ID do recurso (R.id
) da visualização, conforme mostrado no exemplo a seguir:Kotlin
onView(withId(R.id.button_signin))
Java
onView(withId(R.id.button_signin));
Não há garantia de que os IDs de recursos do Android sejam exclusivos. Se o teste tentar corresponder a um ID de recurso usado por mais de uma visualização, o Espresso gerará uma
AmbiguousViewMatcherException
. -
Usar a classe
Matchers
do Hamcrest (link em inglês). É possível usar os métodosallOf()
para combinar vários matchers, comocontainsString()
einstanceOf()
. Essa abordagem permite filtrar melhor os resultados de correspondência, conforme mostrado neste exemplo:Kotlin
onView(allOf(withId(R.id.button_signin), withText("Sign-in")))
Java
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
Você pode usar a palavra-chave
not
para filtrar por visualizações que não correspondem ao matcher, como mostrado no exemplo a seguir:Kotlin
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))))
Java
onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
Para usar esses métodos no teste, importe o pacote
org.hamcrest.Matchers
. Para saber mais sobre a correspondência do Hamcrest, consulte o site do Hamcrest (link em inglês).
Para melhorar o desempenho dos testes do Espresso, especifique as informações mínimas de correspondência necessárias para localizar a visualização de destino. Por exemplo, se uma visualização puder ser exclusivamente identificada pelo texto descritivo dela, não será necessário especificar que ela também pode ser atribuída na instância da TextView
.
Localizar uma visualização em uma AdapterView
Em um widget AdapterView
, a visualização é preenchida dinamicamente com visualizações filhas no momento da execução. Se a visualização de destino que você quer testar estiver dentro de uma AdapterView
(como ListView
, GridView
ou Spinner
), o método onView()
poderá não funcionar, porque apenas um subconjunto das visualizações pode ser carregado na hierarquia de visualização atual.
Em vez disso, chame o método onData()
para ter um objeto DataInteraction
e acessar o elemento da visualização de destino.
O Espresso processa o carregamento do elemento de visualização de destino na hierarquia atual. Ele também é responsável pela rolagem até o elemento de destino e por colocar o elemento em foco.
Observação: o método onData()
não verifica se o item especificado corresponde a uma visualização. O Espresso pesquisa apenas a hierarquia de visualizações atual. Se nenhuma correspondência for encontrada, o método gerará uma NoMatchingViewException
.
O snippet de código a seguir mostra como usar o método onData()
junto a uma correspondência do Hamcrest para pesquisar uma linha específica em uma lista que contém uma determinada string.
Neste exemplo, a classe LongListActivity
contém uma lista de strings expostas por meio de um SimpleAdapter
.
Kotlin
onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo(LongListActivity.ROW_TEXT), `is`("test input"))))
Java
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input"))));
Realizar ações
Chame os métodos ViewInteraction.perform()
ou DataInteraction.perform()
para simular as interações do usuário no componente da IU. É obrigatório transmitir um ou mais objetos ViewAction
como argumentos. O Espresso dispara cada ação em sequência, de acordo com a ordem especificada, e as executa na linha de execução principal.
A classe ViewActions
oferece uma lista de métodos auxiliares para especificar ações comuns.
É possível usar esses métodos como atalhos convenientes em vez de criar e configurar objetos ViewAction
individuais. É possível especificar ações como:
-
ViewActions.click()
: realiza um clique na visualização. -
ViewActions.typeText()
: realiza um clique na visualização e insere uma string especificada. -
ViewActions.scrollTo()
: realiza a rolagem até a visualização. A visualização de destino precisa ser subclassificada a partir deScrollView
, e o valor da propriedadeandroid:visibility
dela precisa serVISIBLE
. Para visualizações que estendemAdapterView
(por exemplo,ListView
), o métodoonData()
lida com a rolagem. -
ViewActions.pressKey()
: realiza o pressionamento de uma tecla por meio de um código de tecla especificado. -
ViewActions.clearText()
: limpa o texto na visualização de destino.
Se a visualização de destino estiver dentro de uma ScrollView
, execute a ação ViewActions.scrollTo()
primeiro para exibir a visualização na tela antes de prosseguir com outras ações. A ação ViewActions.scrollTo()
não terá efeito se a visualização já estiver sendo exibida.
Testar atividades isoladamente com o Espresso Intents
O Espresso Intents permite validar e criar stubs de intents enviados por um app. Com ele, é possível testar um app, uma atividade ou um serviço isoladamente. Para isso, intercepte as intents de saída, crie um stub do resultado e envie-o de volta ao componente em teste.
Para começar a testar com o Espresso Intents, é necessário adicionar a seguinte linha ao arquivo build.gradle do app:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0' }
Para testar uma intent, é preciso criar uma instância da classe IntentsTestRule, que é muito semelhante à classe ActivityTestRule. A classe IntentsTestRule inicializa o Espresso Intents antes de cada teste, encerra a atividade do host e libera o Espresso Intents depois de cada teste.
A classe de teste mostrada no snippet de código a seguir oferece um teste simples para uma intent explícita. Ela testa as atividades e as intents criados no tutorial Criar seu primeiro app.
Kotlin
private const val MESSAGE = "This is a test" private const val PACKAGE_NAME = "com.example.myfirstapp" @RunWith(AndroidJUnit4::class) class SimpleIntentTest { /* Instantiate an IntentsTestRule object. */ @get:Rule var intentsRule: IntentsTestRule<MainActivity> = IntentsTestRule(MainActivity::class.java) @Test fun verifyMessageSentToMessageActivity() { // Types a message into a EditText element. onView(withId(R.id.edit_message)) .perform(typeText(MESSAGE), closeSoftKeyboard()) // Clicks a button to send the message to another // activity through an explicit intent. onView(withId(R.id.send_message)).perform(click()) // Verifies that the DisplayMessageActivity received an intent // with the correct package name and message. intended(allOf( hasComponent(hasShortClassName(".DisplayMessageActivity")), toPackage(PACKAGE_NAME), hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE))) } }
Java
@Large @RunWith(AndroidJUnit4.class) public class SimpleIntentTest { private static final String MESSAGE = "This is a test"; private static final String PACKAGE_NAME = "com.example.myfirstapp"; /* Instantiate an IntentsTestRule object. */ @Rule public IntentsTestRule<MainActivity> intentsRule = new IntentsTestRule<>(MainActivity.class); @Test public void verifyMessageSentToMessageActivity() { // Types a message into a EditText element. onView(withId(R.id.edit_message)) .perform(typeText(MESSAGE), closeSoftKeyboard()); // Clicks a button to send the message to another // activity through an explicit intent. onView(withId(R.id.send_message)).perform(click()); // Verifies that the DisplayMessageActivity received an intent // with the correct package name and message. intended(allOf( hasComponent(hasShortClassName(".DisplayMessageActivity")), toPackage(PACKAGE_NAME), hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE))); } }
Para saber mais sobre o Espresso Intents, consulte a documentação do Espresso Intents no site de AndroidX Test. Você também pode fazer o download das amostras de código IntentsBasicSample e IntentsAdvancedSample (links em inglês).
Testar WebViews com o Espresso Web
O Espresso Web permite testar componentes WebView
de uma atividade. Ele usa a API WebDriver (link em inglês) para inspecionar e controlar o comportamento de uma WebView
.
Para começar a testar com o Espresso Web, é necessário adicionar a seguinte linha ao arquivo build.gradle do app:
dependencies { androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0' }
Quando um teste é criado usando o Espresso Web, é preciso ativar o JavaScript na WebView
ao instanciar o objeto ActivityTestRule para testar a atividade. Nos testes, é possível selecionar os elementos HTML exibidos na WebView
e simular interações do usuário, como inserir texto em uma caixa de texto e clicar em um botão. Quando as ações forem concluídas, você poderá verificar se os resultados na página da Web correspondem aos resultados esperados.
No snippet de código a seguir, a classe testa um componente WebView
com o valor de ID "webview" na atividade em teste.
O teste typeTextInInput_clickButton_SubmitsForm()
seleciona um elemento <input>
na página da Web, insere um texto e verifica o texto que aparece em outro elemento.
Kotlin
private const val MACCHIATO = "Macchiato" private const val DOPPIO = "Doppio" @LargeTest @RunWith(AndroidJUnit4::class) class WebViewActivityTest { @get:Rule val activityRule = object : ActivityTestRule<WebViewActivity>( WebViewActivity::class.java, false, /* Initial touch mode */ false /* launch activity */ ) { override fun afterActivityLaunched() { // Enable JavaScript. onWebView().forceJavascriptEnabled() } } @Test fun typeTextInInput_clickButton_SubmitsForm() { // Lazily launch the Activity with a custom start Intent per test activityRule.launchActivity(withWebFormIntent()) // Selects the WebView in your layout. // If you have multiple WebViews you can also use a // matcher to select a given WebView, onWebView(withId(R.id.web_view)). onWebView() // Find the input element by ID .withElement(findElement(Locator.ID, "text_input")) // Clear previous input .perform(clearElement()) // Enter text into the input element .perform(DriverAtoms.webKeys(MACCHIATO)) // Find the submit button .withElement(findElement(Locator.ID, "submitBtn")) // Simulate a click via JavaScript .perform(webClick()) // Find the response element by ID .withElement(findElement(Locator.ID, "response")) // Verify that the response page contains the entered text .check(webMatches(getText(), containsString(MACCHIATO))) } }
Java
@LargeTest @RunWith(AndroidJUnit4.class) public class WebViewActivityTest { private static final String MACCHIATO = "Macchiato"; private static final String DOPPIO = "Doppio"; @Rule public ActivityTestRule<WebViewActivity> activityRule = new ActivityTestRule<WebViewActivity>(WebViewActivity.class, false /* Initial touch mode */, false /* launch activity */) { @Override protected void afterActivityLaunched() { // Enable JavaScript. onWebView().forceJavascriptEnabled(); } } @Test public void typeTextInInput_clickButton_SubmitsForm() { // Lazily launch the Activity with a custom start Intent per test activityRule.launchActivity(withWebFormIntent()); // Selects the WebView in your layout. // If you have multiple WebViews you can also use a // matcher to select a given WebView, onWebView(withId(R.id.web_view)). onWebView() // Find the input element by ID .withElement(findElement(Locator.ID, "text_input")) // Clear previous input .perform(clearElement()) // Enter text into the input element .perform(DriverAtoms.webKeys(MACCHIATO)) // Find the submit button .withElement(findElement(Locator.ID, "submitBtn")) // Simulate a click via JavaScript .perform(webClick()) // Find the response element by ID .withElement(findElement(Locator.ID, "response")) // Verify that the response page contains the entered text .check(webMatches(getText(), containsString(MACCHIATO))); } }
Para saber mais sobre o Espresso Web, consulte a documentação do Espresso Web no site do AndroidX Test.. Você também pode fazer o download desse snippet de código como parte da amostra de código do Espresso Web (link em inglês).
Verificar resultados
Chame o método ViewInteraction.check()
ou DataInteraction.check()
para declarar que a visualização na IU corresponde a algum estado esperado. É preciso transmitir um objeto ViewAssertion
como argumento. Se a declaração falhar, o Espresso gerará um AssertionFailedError
.
A classe ViewAssertions
oferece uma lista de métodos auxiliares para especificar declarações comuns. As declarações que você pode usar incluem:
-
doesNotExist
: declara que não há uma visualização correspondente aos critérios especificados na hierarquia atual. -
matches
: declara que a visualização especificada existe na hierarquia atual e que o estado dela corresponde a algum matcher do Hamcrest. -
selectedDescendentsMatch
: declara que as visualizações filhas especificadas para uma visualização mãe existem e que o estado delas corresponde a algum matcher do Hamcrest.
O snippet de código a seguir mostra como verificar se o texto exibido na IU tem o mesmo valor que o texto digitado anteriormente no campo EditText
.
Kotlin
fun testChangeText_sameActivity() { // Type text and then press the button. ... // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(STRING_TO_BE_TYPED))) }
Java
public void testChangeText_sameActivity() { // Type text and then press the button. ... // Check that the text was changed. onView(withId(R.id.textToBeChanged)) .check(matches(withText(STRING_TO_BE_TYPED))); }
Executar testes do Espresso em um dispositivo ou emulador
Você pode executar testes do Espresso no Android Studio ou na linha de comando. Especifique AndroidJUnitRunner
como o executor de instrumentação padrão no projeto.
Para executar o teste do Espresso, siga as etapas para executar testes de instrumentação descritas em Conceitos básicos de testes.
Leia também a Referência de API do Espresso.
Outros recursos
Para saber mais sobre o uso do UI Automator em testes do Android, consulte os recursos a seguir.
Amostras
- Amostras de código do Espresso (em inglês): inclui uma seleção completa de amostras do Espresso.
Codelabs
- Codelab de testes do Android (em inglês)