Conceitos básicos do Espresso

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() e onData()). Também expõe APIs que não estão necessariamente vinculadas a nenhuma visualização, como pressBack().
  • ViewMatchers: um conjunto de objetos que implementam a interface Matcher<? super View>. É possível transmitir um ou mais deles ao método onView() para localizar uma visualização na hierarquia atual.
  • ViewActions: um conjunto de objetos ViewAction que podem ser transmitidos para o método ViewInteraction.perform(), como click().
  • ViewAssertions: um conjunto de objetos ViewAssertion que podem ser transmitidos ao método ViewInteraction.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() ou withContentDescription(), 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, o R.id deve ser suficiente.
  • Se a visualização de destino estiver dentro de um AdapterView, como ListView, GridView ou Spinner, o método onView() pode não funcionar. Nesses casos, use onData().

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, uma NoMatchingViewException 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, uma AmbiguousViewMatcherException será gerada. A hierarquia de visualização é impressa, e todas as visualizações correspondentes são marcadas com o rótulo MATCHES:
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