Este documento explica como concluir tarefas de teste comuns e automatizadas por meio da API do Espresso.
A API do Espresso incentiva os autores de testes a pensar o que o usuário pode fazer enquanto interage com o aplicativo, localizando elementos de UI e interagindo com eles. Ao mesmo tempo, o framework evita o acesso direto às atividades e visualizações do app, porque a retenção desses objetos e a operação deles fora da linha de execução de IU é uma das principais fontes de falhas nos testes. Portanto, você não verá métodos como getView()
e getCurrentActivity()
na API do Espresso.
Você ainda pode trabalhar com segurança em visualizações, implementando suas próprias subclasses de ViewAction
e ViewAssertion
.
Componentes da API
Os principais componentes do Espresso incluem:
- Espresso: ponto de entrada para interações com visualizações (via
onView()
eonData()
). Também expõe APIs que não estão necessariamente vinculadas a nenhuma visualização, comopressBack()
. - ViewMatchers: um conjunto de objetos que implementam a interface
Matcher<? super View>
. Você pode transmitir um ou mais deles ao métodoonView()
para localizar uma visualização na hierarquia atual. - ViewActions: um conjunto de objetos
ViewAction
que podem ser transmitidos para o métodoViewInteraction.perform()
, comoclick()
. - ViewAssertions: um conjunto de objetos
ViewAssertion
que podem ser transmitidos ao métodoViewInteraction.check()
. Na maioria das vezes, você usará a declaração de correspondências, que usa um matcher de visualização para declarar o estado da visualização selecionada no momento.
Por exemplo:
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Java
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
Encontrar uma visualização
Na grande maioria dos casos, o método onView()
usa um matcher de Hamcrest que precisa corresponder a uma, e apenas uma, visualização na hierarquia atual. Os matchers são um recurso avançado. Se você já os usou com o Mockito ou o JUnit, já sabe como eles funcionam. Caso não conheça os matchers do Hamcrest, sugerimos que comece com uma leitura rápida desta apresentação (em inglês).
Em geral, a visualização desejada tem um R.id
exclusivo, e um matcher withId
simples reduz a pesquisa de visualização. No entanto, existem muitos casos legítimos em que não é possível determinar o R.id
no momento do desenvolvimento do teste. Por exemplo, a visualização específica pode não ter um R.id
, ou o R.id
não é exclusivo. Isso pode tornar a programação de testes normais instável e complicada, porque a maneira comum de acessar a visualização, com findViewById()
, não funciona. Portanto, pode ser necessário acessar membros privados da atividade ou do fragmento que contém a visualização ou um contêiner com um R.id
conhecido e navegar até o conteúdo dessa visualização específica.
O Espresso lida com esse problema de maneira limpa, permitindo restringir a visualização por meio de objetos ViewMatcher
já existentes ou personalizados.
Encontrar uma visualização pelo R.id
é tão simples quanto chamar onView()
:
Kotlin
onView(withId(R.id.my_view))
Java
onView(withId(R.id.my_view));
Às vezes, os valores R.id
são compartilhados entre várias visualizações. Quando isso acontece, uma tentativa de usar um R.id
específico gera uma exceção, como AmbiguousViewMatcherException
. A mensagem de exceção oferece uma representação em texto da hierarquia atual, em que você pode pesquisar e localizar as visualizações que correspondem ao R.id
não exclusivo:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Analisando os vários atributos das visualizações, você pode encontrar propriedades de identificação exclusiva. No exemplo acima, uma das visualizações tem o texto "Hello!"
. Você pode usá-lo para restringir sua pesquisa por meio de matchers de combinação:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
Você também pode optar por não reverter nenhum dos matchers:
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
Consulte ViewMatchers
para ver os matchers de visualização fornecidos pelo Espresso.
Considerações
- Em um app com comportamento adequado, todas as visualizações com que o usuário pode interagir devem conter texto descritivo ou uma descrição do conteúdo. Consulte Como tornar apps mais acessíveis para ver mais detalhes. Se não for possível restringir uma pesquisa usando
withText()
ouwithContentDescription()
, trate-a como um bug de acessibilidade. - Use o matcher menos descritivo que encontrar a visualização que você está procurando. Não especifique demais, porque isso forçará o framework a trabalhar mais do que o necessário. Por exemplo, se uma visualização tiver identificação exclusiva por meio do texto dela, você não precisará especificar que a visualização também pode ser atribuída na
TextView
. Para muitas visualizações, oR.id
deve ser suficiente. - Se a visualização de destino está dentro de um
AdapterView
, por exemplo,ListView
,GridView
ouSpinner
, o métodoonView()
pode não funcionar. Nesses casos, useonData()
.
Realizar uma ação em uma visualização
Quando você encontra um matcher adequado para a visualização de destino, pode realizar instâncias de ViewAction
nele por meio do método de execução.
Por exemplo, para clicar na visualização:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
Você pode executar uma ou mais ações com uma chamada de execução:
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
Se a visualização com que você está trabalhando está dentro de uma ScrollView
(vertical ou horizontal), anteceda as ações que exigem que a visualização seja exibida, por exemplo, click()
e typeText()
, com scrollTo()
. Isso garante que a visualização seja exibida antes de dar continuidade à outra ação:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Consulte ViewActions
para ver as ações de visualização oferecidas pelo Espresso.
Verificar as declarações de visualização
As declarações podem ser aplicadas à visualização selecionada no momento, com o método check()
. A declaração mais usada é a matches()
. Ele usa um objeto ViewMatcher
para declarar o estado da visualização selecionada no momento.
Por exemplo, para verificar se uma visualização tem o texto "Hello!"
:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
Se quiser declarar que "Hello!"
é o conteúdo da visualização, a seguinte prática é recomendada:
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
Por outro lado, se quiser declarar que uma visualização com o texto "Hello!"
está visível, por exemplo, depois de mudar a sinalização da visibilidade de exibições, o código será aceito.
Teste simples para declaração de visualizações
Neste exemplo, SimpleActivity
contém um Button
e um TextView
. Quando o botão é clicado, o conteúdo de TextView
muda para "Hello Espresso!"
.
Veja como testar isso com o Espresso:
Clicar no botão
O primeiro passo é procurar uma propriedade que ajude a encontrar o botão. O botão na SimpleActivity
tem um R.id
exclusivo, como esperado.
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
Agora, para executar o clique:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
Verificar o texto de TextView
O TextView
com o texto a ser verificado também tem um R.id
exclusivo:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
Agora, para verificar o texto do conteúdo:
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
Verificar o carregamento de dados nas visualizações do adaptador
AdapterView
é um tipo especial de widget que carrega os dados dinamicamente a partir de um adaptador. O exemplo mais comum de um AdapterView
é ListView
. Ao contrário dos widgets estáticos, por exemplo, LinearLayout
, apenas um subconjunto dos elementos filhos AdapterView
pode ser carregado na hierarquia atual. Uma pesquisa onView()
simples não encontraria visualizações que não estão carregadas.
Para lidar com isso, o Espresso disponibiliza um ponto de entrada onData()
separado, capaz de carregar primeiramente o item do adaptador em questão, colocando-o em foco antes de operar nele ou em qualquer um dos elementos filhos.
Aviso: as implementações personalizadas de AdapterView
poderão apresentar problemas com o método onData()
se quebrarem contratos de herança, principalmente a API getItem()
. Nesses casos, a ação mais indicada é refatorar o código do aplicativo. Se a refatoração não for possível, implemente um AdapterViewProtocol
personalizado correspondente. Para ver mais informações, consulte a classe AdapterViewProtocols
padrão disponibilizada pelo Espresso.
Teste simples para visualização do adaptador
Este teste simples demonstra como usar onData()
. SimpleActivity
contém um Spinner
com alguns itens que representam tipos de bebidas de café. Quando um item é selecionado, uma TextView
muda para "One %s a day!"
, em que %s
representa o item selecionado.
O objetivo deste teste é abrir o Spinner
, selecionar um item específico e verificar se a TextView
contém o item. Como a classe Spinner
é baseada em AdapterView
, é recomendado usar onData()
em vez de onView()
para relacionar o item.
Abrir a seleção do item
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
Selecionar um item
Para a seleção do item, o Spinner
cria um ListView
com o próprio conteúdo.
Essa visualização pode ser muito longa, e o elemento pode não ser contribuído para a hierarquia de visualização. Ao usar onData()
, forçamos o elemento visado na hierarquia de visualização. Os itens em Spinner
são strings. Portanto, vamos associar um item que seja igual à string "Americano"
:
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
Verificar se o texto está correto
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
Depurar
O Espresso oferece informações úteis sobre depuração quando um teste falha:
Gerar registros
O Expresso registra todas as ações de visualização no Logcat. Por exemplo:
ViewInteraction: Performing 'single click' action on view with text: Espresso
Hierarquia de visualização
O Espresso imprime a hierarquia de visualização na mensagem de exceção quando onView()
falha.
- Se
onView()
não encontrar a visualização de destino, umaNoMatchingViewException
será gerada. Você pode analisar a hierarquia de visualização na string da exceção para saber por que o matcher não associou nenhuma visualização. - Se
onView()
encontrar várias visualizações correspondentes ao matcher especificado, umaAmbiguousViewMatcherException
será gerada. A hierarquia de visualização é impressa, e todas as visualizações correspondentes são marcadas com o rótuloMATCHES
:
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
Ao lidar com uma hierarquia de visualização complicada ou um comportamento inesperado dos widgets, é sempre útil usar o Hierarchy Viewer no Android Studio para ver uma explicação.
Avisos de visualização do adaptador
O Espresso avisa os usuários sobre a presença de widgets AdapterView
. Quando uma operação onView()
gera uma NoMatchingViewException
e widgets AdapterView
na hierarquia de visualização, a solução mais comum é usar onData()
.
A mensagem da exceção incluirá um aviso com uma lista das visualizações do adaptador.
Você pode usar essas informações para invocar onData()
para carregar a visualização de destino.
Outros recursos
Para saber mais sobre o uso do Espresso em testes do Android, consulte os recursos a seguir.
Amostras
- CustomMatcherSample (em inglês): mostra como estender o Espresso para associar a propriedade hint de um objeto
EditText
. - RecyclerViewSample (em inglês): ações
RecyclerView
para o Espresso. - e mais.