O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

UI de teste para um único app

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:

  1. Localize o componente de IU que você quer testar em uma Activity (por exemplo, um botão de login no app) chamando o método onView() ou o método onData() para controles AdapterView.
  2. Para simular uma interação específica do usuário nesse componente de IU, chame o método ViewInteraction.perform() ou DataInteraction.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.
  3. Repita as etapas acima conforme necessário para simular um fluxo de usuários em várias atividades no app de destino.
  4. 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étodos allOf() para combinar vários matchers, como containsString() e instanceOf(). 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:

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

Codelabs