Conceitos básicos do Espresso

Este documento explica como concluir tarefas de teste automatizadas comuns usando o API do Espresso.

A API Espresso incentiva os autores de testes a pensar do que um usuário pode ao interagir com o aplicativo: localizar elementos da interface e interagir com eles. Ao mesmo tempo, o framework impede o acesso direto a atividades e visualizações do aplicativo, porque se retém esses objetos e opera fora da linha de execução de IU é uma das principais fontes de inconsistência nos testes. Assim, você não consultar métodos como getView() e getCurrentActivity() na API do Espresso. Você ainda pode operar em visualizações com segurança, 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 como pressBack().
  • ViewMatchers: uma coleção de objetos que implementam a interface Matcher<? super View>. Você pode passar um ou mais deles para o Método onView() para localizar uma visualização na hierarquia atual.
  • ViewActions: uma coleção de objetos ViewAction que podem ser transmitidos para o método ViewInteraction.perform(), como click().
  • ViewAssertions: uma coleção de objetos ViewAssertion que podem ser passou o método ViewInteraction.check(). Na maioria das vezes, você usará o corresponde à declaração, que usa um matcher de visualização para declarar o estado do 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 deve corresponder a uma (e somente uma) visualização na visualização atual hierarquia. Quem já usou os matchers é muito conhecido, e eles já estão acostumados. com o Mockito ou o JUnit. Se você não conhece os correspondentes de Hamcrest, sugerimos que você comece com uma rápida análise neste apresentação.

Em geral, a visualização desejada tem um R.id exclusivo, e um matcher withId simples restringir a pesquisa de visualizações. No entanto, existem muitos casos legítimos não foi 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 fazer com que as de instrumentação são frágeis e complicados de escrever, porque a maneira normal de acessar a visualização (com findViewById()) não funciona. Assim, é possível precisar acessar membros privados da atividade ou fragmento que contém a visualização ou encontre um contêiner com um R.id conhecido e navegue até o conteúdo dele para a visualização específica.

O Espresso lida com esse problema de forma limpa, permitindo restringir a visualização. usando objetos ViewMatcher atuais ou seus próprios objetos 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, um tentar usar um determinado R.id gera uma exceção, como AmbiguousViewMatcherException. A mensagem de exceção fornece uma representação da hierarquia de visualizações atual, que pode ser pesquisada e encontrada 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 diversos atributos das visualizações, você pode encontrar resultados únicos propriedades identificáveis. No exemplo acima, uma das visualizações tem o texto "Hello!": Você pode usar isto para restringir sua pesquisa usando uma combinação correspondentes:

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 os matchers de visualização fornecidos pelo Espresso.

Considerações

  • Em um aplicativo com comportamento adequado, todas as visualizações com as quais um usuário pode interagir deve conter texto descritivo ou uma descrição do conteúdo. Consulte Como tornar apps mais acessíveis para mais detalhes. Se você não conseguir restringir uma pesquisa usando withText() ou withContentDescription(), considere tratá-lo como um bug de acessibilidade.
  • Use o matcher menos descritivo que encontrar a visualização que você está procurando pelas quais Não especifique demais, porque isso forçará a estrutura a realizar mais trabalho do que é necessário. Por exemplo, se uma visualização puder ser identificada exclusivamente por seu texto, você não é necessário especificar que a visualização também pode ser atribuída a partir de TextView. Para muitos visualizações, o R.id da visualização deve ser suficiente.
  • Se a visualização de destino estiver dentro de uma AdapterView, como ListView, GridView ou Spinner, o método onView() pode não funcionar. Nessas casos, use onData().

Realizar uma ação em uma visualização

Quando você encontra um matcher adequado para a visualização de destino, é possível executar instâncias de ViewAction nela 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 está dentro de um ScrollView (vertical ou horizontal), considere as ações anteriores que exigem que a visualização seja mostrado, 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 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 no momento com o check() . A declaração mais usada é a matches(). Ele usa uma 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, após uma alteração da sinalização de visibilidade das visualizações, o o código está correto.

Teste simples para declaração de visualizações

Neste exemplo, SimpleActivity contém um Button e um TextView. Quando o for clicado, o conteúdo de TextView mudará 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. A na SimpleActivity tem um R.id exclusivo, conforme 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

O AdapterView é um tipo especial de widget que carrega os dados dinamicamente a partir de com um adaptador. O exemplo mais comum de um AdapterView é ListView. Conforme em oposição a widgets estáticos, como LinearLayout, apenas um subconjunto AdapterView filhos podem ser carregados na hierarquia de visualização atual. Um simples A pesquisa onView() não encontrou visualizações que não estejam carregadas no momento.

O Espresso lida com isso fornecendo um ponto de entrada onData() separado, que é carregar primeiro o item do adaptador em questão, colocando-o em foco antes de que opera nele ou em qualquer um dos filhos dele.

Aviso:implementações personalizadas de AdapterView pode ter problemas com o onData(). se quebrarem contratos de herança, especialmente os API getItem(). Nesses casos, a melhor providência é refatorar o código do aplicativo. Se não puder fazer isso, você pode implementar um AdapterViewProtocol personalizado correspondente. Para mais informações, confira observe o padrão AdapterViewProtocols fornecida 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 for selecionado, há um TextView que muda para "One %s a day!", em que %s representa o item selecionado.

O objetivo desse teste é abrir a Spinner, selecionar um item específico e verificar se TextView contém o item. Como a classe Spinner é baseada em AdapterView, recomendamos usar onData() em vez de onView() para que correspondem 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 visualização hierarquia. Usando onData(), forçamos o elemento desejado a aparecer na visualização. hierarquia. Os itens no Spinner são strings, então queremos associar um item que é 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ções na mensagem de exceção quando onView() falhar.

  • Se o onView() não encontrar a visualização de destino, um NoMatchingViewException será gerada. É possível examinar a hierarquia de visualizações na string da 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 é gerada. A hierarquia de visualizações é impressa, 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ções complicada ou um comportamento inesperado dos widgets é sempre útil usar 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 um onView() uma operação gera um NoMatchingViewException e widgets AdapterView são presentes na hierarquia de visualizações, 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 mais informações sobre o uso do Espresso em testes do Android, consulte o recursos a seguir.

Amostras