Este documento explica como concluir tarefas de teste comuns e automatizadas usando a API do Espresso.
A API Espresso incentiva os autores de teste a pensar no que um usuário pode
fazer ao interagir com o aplicativo, localizando elementos da IU e interagindo
com eles. Ao mesmo tempo, o framework impede o acesso direto a atividades
e visualizações do aplicativo, porque a retenção desses objetos e a operação
deles fora da linha de execução de IU é uma fonte importante de inconsistência dos testes. Portanto, você
não vai encontrar métodos como getView()
e getCurrentActivity()
na API do Espresso.
Você ainda pode operar 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>
. É possível 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ê usa a declaração de correspondências, que usa um matcher de visualização para declarar o estado da visualização selecionada no momento.
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 potentes e você já sabe por quem já os usou
com o Mockito ou o JUnit. Se você não estiver familiarizado com os matchers do Hamcrest, sugerimos que comece com uma leitura rápida desta apresentação (em inglês).
Geralmente, a visualização visada tem um R.id
exclusivo e um matcher withId
simples reduz
a pesquisa de visualização. No entanto, há muitos casos legítimos em que
não é possível determinar 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 frágeis e complicado, porque a maneira normal de
acessar a visualização, com findViewById()
, não funciona. Portanto, pode
ser necessário acessar membros particulares da atividade ou do fragmento que contém a visualização ou
encontrar 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
usando 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 fornece uma representação
em texto da hierarquia atual, em que você pode pesquisar e encontrar
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
exclusivamente identificáveis. No exemplo acima, uma das visualizações tem o texto
"Hello!"
. É possível usá-lo para restringir sua pesquisa usando 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 aplicativo com comportamento adequado, todas as visualizações com que um usuário pode interagir
precisam conter texto descritivo ou uma descrição de 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 puder ser identificada exclusivamente pelo texto, você
não vai precisar especificar que ela também pode ser atribuída no
TextView
. Para muitas visualizações, oR.id
deve ser suficiente. - Se a visualização de destino estiver dentro de um
AdapterView
, comoListView
,GridView
ouSpinner
, o métodoonView()
pode não funcionar. Nesses casos, useonData()
.
Realizar uma ação em uma visualização
Quando você encontra um correspondente adequado para a visualização de destino, é possível
executar instâncias de ViewAction
nele usando o 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 estiver dentro de um ScrollView
(vertical ou
horizontal), considere as ações anteriores que exigem que a visualização seja
mostrada, como click()
e typeText()
, com scrollTo()
. Isso
garante que a visualização seja exibida antes de prosseguir para a outra ação:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Consulte ViewActions
para ver as ações de visualização fornecidas pelo Espresso.
Verificar as declarações de visualização
As declarações podem ser aplicadas à visualização selecionada com o método
check()
. A declaração mais usada é a matches()
. Ela 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 você quiser declarar que uma visualização com o texto "Hello!"
é
visível, por exemplo, depois de uma mudança na flag de visibilidade de visualizações, o
código é adequado.
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 em 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 de widgets estáticos, como LinearLayout
, apenas um subconjunto dos
filhos de AdapterView
pode ser carregado na hierarquia de visualização atual. Uma pesquisa
onView()
simples não encontraria visualizações que não estão carregadas no momento.
Para lidar com isso, o Espresso fornece um ponto de entrada onData()
separado,
que pode carregar primeiro o item do adaptador em questão, colocando-o em foco antes de
operar nele ou em qualquer um dos filhos.
Aviso:as implementações personalizadas de
AdapterView
podem apresentar problemas com o método onData()
se violarem contratos de herança, principalmente a
API getItem()
. Nesses casos, a melhor providência é
refatorar o código do aplicativo. Se não for possível fazer isso, implemente uma
AdapterViewProtocol
personalizada correspondente. Para saber mais, consulte
a classe
AdapterViewProtocols
padrão fornecida pelo Espresso.
Teste simples para visualização do adaptador
Este teste simples demonstra como usar onData()
. SimpleActivity
contém uma
Spinner
com alguns itens que representam tipos de bebida com café. Quando um
item é selecionado, uma TextView
muda para "One %s a day!"
, em que
%s
representa o item selecionado.
O objetivo desse teste é abrir o Spinner
, selecionar um item específico e
verificar se o TextView
contém o item. Como a classe Spinner
é baseada
em AdapterView
, é recomendável usar onData()
em vez de onView()
para
corresponder ao 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 desejado na hierarquia
de visualização. Os itens em Spinner
são strings. Portanto, queremos corresponder 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 examinar a hierarquia de visualização na string de exceção para analisar por que o matcher não associou nenhuma visualização. - Se
onView()
encontrar várias visualizações que correspondam 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 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
estão
presentes 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 abaixo.
Exemplos
- CustomMatcherSample: mostra como estender o Espresso para corresponder à propriedade hint de um objeto
EditText
. - RecyclerViewSample (em inglês):
ações
RecyclerView
para o Espresso. - Outros recursos para testes