Espresso Testing Framework - Guia rápido
Em geral, o teste de automação móvel é uma tarefa difícil e desafiadora. A disponibilidade do Android para diferentes dispositivos e plataformas torna as coisas tediosas para testes de automação móvel. Para facilitar, o Google aceitou o desafio e desenvolveu a estrutura Espresso. Ele fornece uma API muito simples, consistente e flexível para automatizar e testar as interfaces do usuário em um aplicativo Android. Os testes do Espresso podem ser escritos em Java e Kotlin, uma linguagem de programação moderna para desenvolver aplicativos Android.
A API do Espresso é simples e fácil de aprender. Você pode realizar facilmente testes de IU do Android sem a complexidade dos testes multithread. Google Drive, Maps e alguns outros aplicativos estão usando o Espresso.
Características do Espresso
Alguns dos principais recursos suportados pelo Espresso são os seguintes,
API muito simples e, portanto, fácil de aprender.
Altamente escalonável e flexível.
Fornece módulo separado para testar o componente Android WebView.
Fornece um módulo separado para validar e simular Intents Android.
Fornece sincronização automática entre seu aplicativo e os testes.
Vantagens do Espresso
Vamos agora quais são os benefícios do Espresso.
Compatibilidade com versões anteriores
Fácil de configurar.
Ciclo de teste altamente estável.
Suporta atividades de teste fora do aplicativo também.
Suporta JUnit4
Automação de IU adequada para escrever testes de caixa preta.
Neste capítulo, vamos entender como instalar o espresso framework, configurá-lo para escrever testes espresso e executá-lo em nosso aplicativo Android.
Pré-requisitos
Espresso é uma estrutura de teste de interface do usuário para testar aplicativos Android desenvolvidos na linguagem Java / Kotlin usando Android SDK. Portanto, o único requisito do espresso é desenvolver o aplicativo usando Android SDK em Java ou Kotlin e é aconselhável ter o Android Studio mais recente.
A lista de itens a serem configurados corretamente antes de começarmos a trabalhar na estrutura do espresso é a seguinte -
Instale o Java JDK mais recente e configure a variável de ambiente JAVA_HOME.
Instale o Android Studio mais recente (versão 3.2. Ou superior).
Instale o Android SDK mais recente usando o SDK Manager e configure a variável de ambiente ANDROID_HOME.
Instale a ferramenta Gradle Build mais recente e configure a variável de ambiente GRADLE_HOME.
Configurar EspressoTesting Framework
Inicialmente, a estrutura de teste do espresso é fornecida como parte da biblioteca de suporte do Android. Mais tarde, a equipe do Android fornece uma nova biblioteca Android, AndroidX, e move o desenvolvimento da estrutura de teste mais recente do espresso para a biblioteca. O desenvolvimento mais recente (Android 9.0, API de nível 28 ou superior) da estrutura de teste do espresso será feito na biblioteca AndroidX.
Incluir a estrutura de teste do espresso em um projeto é tão simples quanto configurar a estrutura de teste do espresso como uma dependência no arquivo gradle do aplicativo, app / build.gradle. A configuração completa é a seguinte,
Usando a biblioteca de suporte do Android,
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}
Usando a biblioteca AndroidX,
android {
defaultConfig {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.androidx.test:runner:1.0.2'
androidTestImplementation 'com.androidx.espresso:espresso-core:3.0.2'
}
testInstrumentationRunner em android / defaultConfig define a classe AndroidJUnitRunner para executar os testes de instrumentação. A primeira linha nas dependências inclui a estrutura de teste JUnit , a segunda linha nas dependências inclui a biblioteca do executor de teste para executar os casos de teste e, finalmente, a terceira linha nas dependências inclui a estrutura de teste espresso.
Por padrão, o Android Studio define a estrutura de teste do espresso (biblioteca de suporte do Android) como uma dependência enquanto cria o projeto android e o gradle fará o download da biblioteca necessária do repositório Maven. Vamos criar um aplicativo simples para Android Hello world e verificar se a estrutura de teste do espresso está configurada corretamente.
As etapas para criar um novo aplicativo Android são descritas abaixo -
Inicie o Android Studio.
Selecione Arquivo → Novo → Novo projeto.
Digite o nome do aplicativo (HelloWorldApp) e o domínio da empresa (espressosamples.tutorialspoint.com) e clique em Avançar .
Para criar um projeto Android,
Selecione API mínima como API 15: Android 4.0.3 (IceCreamSandwich) e clique em Avançar.
Para segmentar dispositivos Android,
Selecione Atividade vazia e clique em Avançar .
Para adicionar uma atividade ao celular,
Insira o nome da atividade principal e clique em Concluir .
Para configurar a atividade,
Assim que um novo projeto for criado, abra o arquivo app / build.gradle e verifique seu conteúdo. O conteúdo do arquivo é especificado abaixo,
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.tutorialspoint.espressosamples.helloworldapp"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espressocore:3.0.2'
}
A última linha especifica a dependência da estrutura de teste do espresso. Por padrão, a biblioteca de suporte do Android está configurada. Podemos reconfigurar o aplicativo para usar a biblioteca AndroidX clicando em Refactor → Migrate to AndroidX no menu.
Para migrar para o Androidx,
Agora, o app / build.gradle muda conforme especificado abaixo,
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.tutorialspoint.espressosamples.helloworldapp"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Agora, a última linha inclui a estrutura de teste espresso da biblioteca AndroidX.
Configurações do dispositivo
Durante o teste, é recomendado desligar a animação no dispositivo Android, que é usado para o teste. Isso reduzirá as confusões ao verificar os recursos de ócio.
Vamos ver como desativar a animação em dispositivos Android - (Configurações → Opções do desenvolvedor),
Escala de animação da janela
Escala de animação de transição
Escala de duração do animador
Se o menu de opções do desenvolvedor não estiver disponível na tela Configurações , clique várias vezes em Número da versão disponível na opção Sobre o telefone . Isso ativa o menu Opções do desenvolvedor .
Neste capítulo, vamos ver como executar testes usando o Android Studio.
Cada aplicativo Android tem dois tipos de testes -
Testes funcionais / unitários
Testes de instrumentação
O teste funcional não precisa que o aplicativo Android real seja instalado e iniciado no dispositivo ou emulador e teste a funcionalidade. Ele pode ser iniciado no próprio console sem invocar o aplicativo real. No entanto, os testes de instrumentação precisam que o aplicativo real seja iniciado para testar a funcionalidade como interface de usuário e interação com o usuário. Por padrão, os testes de unidade são escritos emsrc/test/java/ pasta e os testes de instrumentação são escritos em src/androidTest/java/pasta. O Android Studio fornece o menu de contexto Executar para as classes de teste para executar o teste escrito nas classes de teste selecionadas. Por padrão, um aplicativo Android tem duas classes - ExampleUnitTest na pasta src / test e ExampleInstrumentedTest na pasta src / androidTest .
Para executar o teste de unidade padrão, selecione ExampleUnitTest no Android Studio, clique com o botão direito sobre ele e clique em Executar 'ExampleUnitTest' conforme mostrado abaixo,
Executar teste de unidade
Isso executará o teste de unidade e mostrará o resultado no console como na imagem a seguir -
Sucesso no teste de unidade
Para executar o teste de instrumentação padrão, selecione ExampleInstrumentationTest no android studio, clique com o botão direito e clique em Executar 'ExampleInstrumentationTest' conforme mostrado abaixo,
Executar teste de instrumentação
Isso executará o teste de unidade iniciando o aplicativo em qualquer dispositivo ou emulador e mostrará o resultado no console como na imagem a seguir -
O teste de instrumentação foi executado com sucesso.
Neste capítulo, vamos entender os fundamentos do JUnit , a estrutura de teste de unidade popular desenvolvida pela comunidade Java na qual a estrutura de teste do espresso é construída.
JUnit é o padrão de fato para o teste de unidade de um aplicativo Java. Mesmo assim, é popular para teste de unidade, ele tem suporte completo e provisão para teste de instrumentação também. A biblioteca de teste do Espresso estende as classes JUnit necessárias para dar suporte ao teste de instrumentação baseado em Android.
Escreva um Teste de Unidade Simples
Vamos criar uma classe Java, Computation (Computation.java) e escrever uma operação matemática simples, Soma e Multiplicação . Em seguida, escreveremos casos de teste usando JUnit e verificaremos executando os casos de teste.
Inicie o Android Studio.
Abra HelloWorldApp criado no capítulo anterior.
Crie um arquivo, Computation.java em app / src / main / java / com / tutorialspoint / espressosamples / helloworldapp / e escreva duas funções - Soma e Multiplica conforme especificado abaixo,
package com.tutorialspoint.espressosamples.helloworldapp;
public class Computation {
public Computation() {}
public int Sum(int a, int b) {
return a + b;
}
public int Multiply(int a, int b) {
return a * b;
}
}
Crie um arquivo, ComputationUnitTest.java em app / src / test / java / com / tutorialspoint / espressosamples / helloworldapp e escreva casos de teste de unidade para testar a funcionalidade Soma e Multiplicação, conforme especificado abaixo
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
@Test
public void sum_isCorrect() {
Computation computation = new Computation();
assertEquals(4, computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
Computation computation = new Computation();
assertEquals(4, computation.Multiply(2,2));
}
}
Aqui, usamos dois novos termos - @Test e assertEquals . Em geral, JUnit usa anotação Java para identificar os casos de teste em uma classe e informações sobre como executar os casos de teste. @Test é uma dessas anotações Java, que especifica que a função particular é um caso de teste junit. assertEquals é uma função para afirmar que o primeiro argumento (valor esperado) e o segundo argumento (valor calculado) são iguais e iguais. JUnit fornece vários métodos de asserção para diferentes cenários de teste.
Agora, execute o ComputationUnitTest no Android Studio clicando com o botão direito na classe e invocando a opção Executar 'ComputationUnitTest' conforme explicado no capítulo anterior. Isso executará os casos de teste de unidade e relatará o sucesso.
O resultado do teste de unidade de computação é mostrado abaixo -
Anotações
A estrutura JUnit usa anotações extensivamente . Algumas das anotações importantes são as seguintes -
@Test
@Before
@After
@BeforeClass
@AfterClass
@Rule
Anotação @Test
@Test é a anotação muito importante na estrutura JUnit . @Test é usado para diferenciar um método normal do método de caso de teste. Depois que um método é decorado com a anotação @Test , esse método específico é considerado um caso de teste e será executado pelo JUnit Runner . JUnit Runner é uma classe especial, que é usada para encontrar e executar os casos de teste JUnit disponíveis dentro das classes java. Por enquanto, estamos usando a opção integrada do Android Studio para executar os testes de unidade (que, por sua vez, executam o JUnit Runner ). Um exemplo de código é o seguinte,
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
@Test
public void multiply_isCorrect() {
Computation computation = new Computation();
assertEquals(4, computation.Multiply(2,2));
}
}
@Antes
A anotação @Before é usada para se referir a um método, que precisa ser invocado antes de executar qualquer método de teste disponível em uma classe de teste específica. Por exemplo, em nosso exemplo, o objeto Computation pode ser criado em um método separado e anotado com @Before para que seja executado antes dos casos de teste sum_isCorrect e multiply_isCorrect . O código completo é o seguinte,
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
Computation computation = null;
@Before
public void CreateComputationObject() {
this.computation = new Computation();
}
@Test
public void sum_isCorrect() {
assertEquals(4, this.computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
assertEquals(4, this.computation.Multiply(2,2));
}
}
@Depois de
@After é semelhante a @Before , mas o método anotado com @After será chamado ou executado após a execução de cada caso de teste. O código de amostra é o seguinte,
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
Computation computation = null;
@Before
public void CreateComputationObject() {
this.computation = new Computation();
}
@After
public void DestroyComputationObject() {
this.computation = null;
}
@Test
public void sum_isCorrect() {
assertEquals(4, this.computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
assertEquals(4, this.computation.Multiply(2,2));
}
}
@Antes da aula
@BeforeClass é semelhante a @Before , mas o método anotado com @BeforeClass será chamado ou executado apenas uma vez antes de executar todos os casos de teste em uma classe particular. É útil criar um objeto de uso intensivo de recursos, como um objeto de conexão de banco de dados. Isso reduzirá o tempo de execução de uma coleção de casos de teste. Este método precisa ser estático para funcionar corretamente. Em nosso exemplo, podemos criar o objeto de computação uma vez antes de executar todos os casos de teste, conforme especificado abaixo,
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
private static Computation computation = null;
@BeforeClass
public static void CreateComputationObject() {
computation = new Computation();
}
@Test
public void sum_isCorrect() {
assertEquals(4, computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
assertEquals(4, computation.Multiply(2,2));
}
}
@Depois da aula
@AfterClass é semelhante a @BeforeClass , mas o método anotado com @AfterClass será chamado ou executado apenas uma vez depois que todos os casos de teste em uma classe particular forem executados. Este método também precisa ser estático para funcionar corretamente. O código de amostra é o seguinte -
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
private static Computation computation = null;
@BeforeClass
public static void CreateComputationObject() {
computation = new Computation();
}
@AfterClass
public static void DestroyComputationObject() {
computation = null;
}
@Test
public void sum_isCorrect() {
assertEquals(4, computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
assertEquals(4, computation.Multiply(2,2));
}
}
@Regra
A anotação @Rule é um dos destaques do JUnit . É usado para adicionar comportamento aos casos de teste. Podemos apenas anotar os campos do tipo TestRule . Ele realmente fornece conjunto de recursos fornecidos pelo @Before e @After anotação, mas de uma forma eficiente e reutilizável. Por exemplo, podemos precisar de uma pasta temporária para armazenar alguns dados durante um caso de teste. Normalmente, precisamos criar uma pasta temporária antes de executar o caso de teste (usando a anotação @Before ou @BeforeClass) e destruí-la após a execução do caso de teste (usando a anotação @After ou @AfterClass). Em vez disso, podemos usar a classe TemporaryFolder (do tipo TestRule ) fornecida pela estrutura JUnit para criar uma pasta temporária para todos os nossos casos de teste e a pasta temporária será excluída como e quando o caso de teste for executado. Precisamos criar uma nova variável do tipo TemporaryFolder e anotar com @Rule conforme especificado abaixo,
package com.tutorialspoint.espressosamples.helloworldapp;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
public class ComputationUnitTest {
private static Computation computation = null;
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void file_isCreated() throws IOException {
folder.newFolder("MyTestFolder");
File testFile = folder.newFile("MyTestFile.txt");
assertTrue(testFile.exists());
}
@BeforeClass
public static void CreateComputationObject() {
computation = new Computation();
}
@AfterClass
public static void DestroyComputationObject() {
computation = null;
}
@Test
public void sum_isCorrect() {
assertEquals(4, computation.Sum(2,2));
}
@Test
public void multiply_isCorrect() {
assertEquals(4, computation.Multiply(2,2));
}
}
Ordem de Execução
No JUnit , os métodos anotados com anotações diferentes serão executados em ordem específica conforme mostrado abaixo,
@BeforeClass
@Rule
@Before
@Test
@After
@AfterClass
Afirmação
Asserção é uma forma de verificar se o valor esperado do caso de teste corresponde ao valor real do resultado do caso de teste. JUnit fornece asserção para diferentes cenários; algumas afirmações importantes estão listadas abaixo -
fail() - Para explicitamente fazer um caso de teste falhar.
assertTrue(boolean test_condition) - Verifica se test_condition é verdadeiro
assertFalse(boolean test_condition) - Verifica se test_condition é falsa
assertEquals(expected, actual) - Verifica se ambos os valores são iguais
assertNull(object) - Verifica se o objeto é nulo
assertNotNull(object) - Verifica se o objeto não é nulo
assertSame(expected, actual) - Verifica se ambos se referem ao mesmo objeto.
assertNotSame(expected, actual) - Verifica se ambos se referem a objetos diferentes.
Neste capítulo, vamos aprender os termos da estrutura de teste do espresso, como escrever um caso de teste simples do espresso e o fluxo de trabalho completo ou arquitetura da estrutura de teste do espresso.
Visão geral
O Espresso oferece um grande número de classes para testar a interface do usuário e a interação do usuário com um aplicativo Android. Eles podem ser agrupados em cinco categorias, conforme especificado abaixo -
Corredor JUnit
A estrutura de teste do Android fornece um executor, AndroidJUnitRunner, para executar os casos de teste espresso escritos nos casos de teste estilo JUnit3 e JUnit4. É específico para o aplicativo Android e lida de forma transparente com o carregamento dos casos de teste espresso e do aplicativo em teste no dispositivo real ou emulador, executa os casos de teste e relata o resultado dos casos de teste. Para usar AndroidJUnitRunner no caso de teste, precisamos anotar a classe de teste usando a anotação @RunWith e, em seguida, passar o argumento AndroidJUnitRunner conforme especificado abaixo -
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
}
Regras JUnit
A estrutura de teste do Android fornece uma regra, ActivityTestRule, para iniciar uma atividade do Android antes de executar os casos de teste. Ele inicia a atividade antes de cada método anotado com @ Test` e @Before. Ele encerrará a atividade após o método anotado com @After. Um exemplo de código é o seguinte,
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
Aqui, MainActivity é a atividade a ser iniciada antes de executar um caso de teste e destruída após a execução do caso de teste específico.
ViewMatchers
O Espresso fornece um grande número de classes de combinação de visualização (no pacote androidx.test.espresso.matcher.ViewMatchers ) para combinar e encontrar elementos / visualizações da IU em uma hierarquia de visualização da tela de atividade do Android. O método onView do Espresso recebe um único argumento do tipo Matcher (View matchers), encontra a visualização da IU correspondente e retorna o objeto ViewInteraction correspondente . O objeto ViewInteraction retornado pelo método onView pode ser usado posteriormente para invocar ações como clicar na visualização combinada ou pode ser usado para declarar a visualização combinada. Um código de amostra para encontrar a visualização com o texto “Hello World!” é o seguinte,
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
Aqui, withText é um matcher, que pode ser usado para corresponder à visualização da IU com o texto “Hello World!”
ViewActions
O Espresso fornece um grande número de classes de ação de visualização (em androidx.test.espresso.action.ViewActions) para invocar a ação diferente na visualização selecionada / correspondente. Uma vez que onView corresponda e retorne o objeto ViewInteraction , qualquer ação pode ser invocada chamando o método “executar” do objeto ViewInteraction e transmitindo-o com as ações de visualização adequadas. Um exemplo de código para clicar na visualização correspondente é o seguinte,
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.perform(click());
Aqui, a ação de clique da visualização correspondente será invocada.
ViewAssertions
Semelhante a view matchers e view actions, o Espresso fornece um grande número de view assertion (no pacote androidx.test.espresso.assertion.ViewAssertions ) para afirmar que a view correspondente é o que esperávamos. Depois que onView corresponde e retorna o objeto ViewInteraction , qualquer declaração pode ser verificada usando o método de verificação de ViewInteraction , passando-o com a declaração de visão apropriada. Um código de amostra para afirmar que a visualização correspondente é a seguinte,
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.check(matches(withId(R.id.text_view)));
Aqui, as correspondências aceitam o matcher da visão e retornam a asserção da visão, que pode ser verificada pelo método de verificação de ViewInteraction .
Fluxo de trabalho do Espresso Testing Framework
Vamos entender como a estrutura de teste do espresso funciona e como ela oferece opções para fazer qualquer tipo de interação com o usuário de uma forma simples e flexível. O fluxo de trabalho de um caso de teste do espresso é conforme descrito abaixo,
Como aprendemos anteriormente, o executor Android JUnit, AndroidJUnit4 , executará os casos de teste do Android. Os casos de teste do espresso precisam ser marcados com @RunWith (AndroidJUnut.class) . Primeiro, o AndroidJUnit4 preparará o ambiente para executar os casos de teste. Ele inicia o dispositivo ou emulador Android conectado, instala o aplicativo e garante que o aplicativo a ser testado esteja pronto. Ele executará os casos de teste e relatará os resultados.
O Espresso precisa de pelo menos uma única regra JUnit do tipo ActivityTestRule para especificar a atividade. O executor Android JUnit iniciará a atividade a ser iniciada usando ActivityTestRule .
Cada caso de teste precisa de um mínimo de chamada de método onView ou onDate (usado para localizar visualizações baseadas em dados como AdapterView ) para corresponder e localizar a visualização desejada. onView ou onData retorna o objeto ViewInteraction .
Depois que o objeto ViewInteraction é retornado, podemos invocar uma ação da visão selecionada ou verificar a visão de nossa visão esperada usando asserção.
A ação pode ser chamada usando o método perform do objeto ViewInteraction , passando qualquer uma das ações de visualização disponíveis.
A asserção pode ser chamada usando o método check do objeto ViewInteraction , passando qualquer uma das asserções de visualização disponíveis.
A representação do diagrama do Fluxo de Trabalho é a seguinte,
Exemplo - ver asserção
Vamos escrever um caso de teste simples para encontrar a visualização de texto com "Hello World!" texto em nosso aplicativo “HelloWorldApp” e, em seguida, declare-o usando a asserção de visão. O código completo é o seguinte,
package com.tutorialspoint.espressosamples.helloworldapp;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.withText;;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void view_isCorrect() {
onView(withText("Hello World!")).check(matches(isDisplayed()));
}
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.tutorialspoint.espressosamples.helloworldapp", appContext.getPackageName());
}
}
Aqui, temos utilizado withText vista matchers para encontrar a exibição de texto com “Olá mundo!” text e corresponde à asserção de exibição para afirmar que a exibição de texto é exibida corretamente. Assim que o caso de teste for invocado no Android Studio, ele executará o caso de teste e relatará a mensagem de sucesso conforme abaixo.
view_isCorrect test case
A estrutura do Espresso oferece muitos matchers de visualização. O objetivo do matcher é combinar uma visão usando diferentes atributos da visão como Id, Texto e disponibilidade de visão filha. Cada matcher corresponde a um determinado atributo da visão e se aplica a um determinado tipo de visão. Por exemplo, withId matcher corresponde à propriedade Id da visualização e se aplica a todas as visualizações, enquanto withText matcher corresponde à propriedade Text da visualização e se aplica apenas a TextView .
Neste capítulo, vamos aprender os diferentes matchers fornecidos pela estrutura de teste do espresso, bem como aprender a biblioteca Hamcrest na qual os matchers do espresso são construídos.
Biblioteca Hamcrest
A biblioteca Hamcrest é uma biblioteca importante no escopo da estrutura de teste do espresso. O próprio Hamcrest é uma estrutura para escrever objetos correspondentes. A estrutura do Espresso usa extensivamente a biblioteca Hamcrest e a estende sempre que necessário para fornecer matchers simples e extensíveis.
Hamcrest fornece uma função simples assertThat e uma coleção de matchers para afirmar qualquer objeto. assertThat tem três argumentos e eles são mostrados abaixo -
String (descrição do teste, opcional)
Objeto (real)
Matcher (esperado)
Vamos escrever um exemplo simples para testar se um objeto de lista tem valor esperado.
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
@Test
public void list_hasValue() {
ArrayList<String> list = new ArrayList<String>();
list.add("John");
assertThat("Is list has John?", list, hasItem("John"));
}
Aqui, hasItem retorna um matcher, que verifica se a lista real especificou o valor como um dos itens.
O Hamcrest tem muitos combinadores integrados e também opções para criar novos combinadores. Alguns dos matchers integrados importantes úteis na estrutura de teste do espresso são os seguintes -
qualquer coisa - sempre combinando
Matchers baseados em lógica
allOf - aceitar qualquer número de matchers e correspondências somente se todos os matchers forem bem-sucedidos.
anyOf - aceita qualquer número de correspondências e correspondências, se qualquer uma delas for bem-sucedida.
not - aceita um matcher e combina apenas se o matcher falhou e vice-versa.
Matchers baseados em texto
equalToIgnoringCase - usado para testar se a entrada real é igual à string esperada, ignorando maiúsculas e minúsculas.
equalToIgnoringWhiteSpace - usado para testar se a entrada real é igual à string especificada, ignorando maiúsculas e minúsculas e espaços em branco.
containsString - usado para testar se a entrada real contém a string especificada.
endsWith - usado para testar se a entrada real começa com a string especificada.
startsWith - usado para testar se a entrada finaliza com a string especificada.
Matchers baseados em números
closeTo - usado para testar se a entrada real está próxima do número esperado.
greaterThan - usado para testar se a entrada real é maior do que o número esperado.
greaterThanOrEqualTo - usado para testar se a entrada real é maior ou igual ao número esperado.
lessThan - usado para testar se a entrada real é menor que o número esperado.
lessThanOrEqualTo - usado para testar se a entrada real é menor ou igual ao número esperado.
Matchers baseados em objetos
equalTo - usado para testar se a entrada real é igual ao objeto esperado
hasToString - usado para testar se a entrada real tem o método toString.
instanceOf - usado para testar se a entrada real é a instância da classe esperada.
isCompatibleType - usado para testar se a entrada real é compatível com o tipo esperado.
notNullValue - usado para testar se a entrada real não é nula.
sameInstance - usado para testar se a entrada real e esperada são da mesma instância.
hasProperty - usado para testar se a entrada real tem a propriedade esperada
is - Sugar ou atalho para equalTo
Matchers
O Espresso fornece o método onView () para corresponder e localizar as visualizações. Ele aceita correspondências de exibição e retorna o objeto ViewInteraction para interagir com a exibição correspondida. A lista frequentemente usada de matchers de visualização é descrita abaixo -
withId ()
withId () aceita um argumento do tipo int e o argumento se refere ao id da visão. Ele retorna um matcher, que corresponde à visão usando o id da visão. O código de amostra é o seguinte,
onView(withId(R.id.testView))
withText ()
withText () aceita um argumento do tipo string e o argumento refere-se ao valor da propriedade de texto da visualização. Ele retorna um matcher, que corresponde à visão usando o valor de texto da visão. Aplica-se apenas a TextView . O código de amostra é o seguinte,
onView(withText("Hello World!"))
withContentDescription ()
withContentDescription () aceita um argumento do tipo string e o argumento se refere ao valor da propriedade de descrição do conteúdo da visualização. Ele retorna um matcher, que corresponde à visão usando a descrição da visão. O código de amostra é o seguinte,
onView(withContentDescription("blah"))
Também podemos passar o id do recurso do valor do texto em vez do próprio texto.
onView(withContentDescription(R.id.res_id_blah))
hasContentDescription ()
hasContentDescription () não tem argumento. Ele retorna um matcher, que corresponde à visualização que possui qualquer descrição de conteúdo. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), hasContentDescription()))
withTagKey ()
withTagKey () aceita um argumento do tipo string e o argumento se refere à chave de tag da visualização. Ele retorna um matcher, que corresponde à visualização usando sua chave de tag. O código de amostra é o seguinte,
onView(withTagKey("blah"))
Também podemos passar o id do recurso do nome da tag em vez do próprio nome da tag.
onView(withTagKey(R.id.res_id_blah))
withTagValue ()
withTagValue () aceita um argumento do tipo Matcher <Object> e o argumento se refere ao valor da tag da visualização. Ele retorna um matcher, que corresponde à visualização usando seu valor de tag. O código de amostra é o seguinte,
onView(withTagValue(is((Object) "blah")))
Aqui está o matcher de Hamcrest.
withClassName ()
withClassName () aceita um argumento do tipo Matcher <String> e o argumento se refere ao valor do nome da classe da visualização. Ele retorna um matcher, que corresponde à visualização usando seu nome de classe. O código de amostra é o seguinte,
onView(withClassName(endsWith("EditText")))
Aqui, endsWith é o matcher Hamcrest e Return Matcher <String>
withHint ()
withHint () aceita um argumento do tipo Matcher <String> e o argumento se refere ao valor de dica da visualização. Ele retorna um matcher, que corresponde à visão usando a dica da visão. O código de amostra é o seguinte,
onView(withClassName(endsWith("Enter name")))
withInputType ()
withInputType () aceita um argumento do tipo int e o argumento se refere ao tipo de entrada da visualização. Ele retorna um matcher, que corresponde à visualização usando seu tipo de entrada. O código de amostra é o seguinte,
onView(withInputType(TYPE_CLASS_DATETIME))
Aqui, TYPE_CLASS_DATETIME refere-se à visualização de edição de datas e horas de suporte.
withResourceName ()
withResourceName () aceita um argumento do tipo Matcher <String> e o argumento se refere ao valor do nome da classe da visualização. Ele retorna um matcher, que corresponde à visão usando o nome do recurso da visão. O código de amostra é o seguinte,
onView(withResourceName(endsWith("res_name")))
Ele também aceita argumentos de string. O código de amostra é o seguinte,
onView(withResourceName("my_res_name"))
withAlpha ()
withAlpha () aceita um argumento do tipo float e o argumento se refere ao valor alfa da visualização. Ele retorna um matcher, que corresponde à visualização usando o valor alfa da visualização. O código de amostra é o seguinte,
onView(withAlpha(0.8))
withEffectiveVisibility ()
withEffectiveVisibility () aceita um argumento do tipo ViewMatchers.Visibility e o argumento se refere à visibilidade efetiva da visualização. Ele retorna um matcher, que corresponde à visualização usando a visibilidade da visualização. O código de amostra é o seguinte,
onView(withEffectiveVisibility(withEffectiveVisibility.INVISIBLE))
withSpinnerText ()
withSpinnerText () aceita um argumento do tipo Matcher <String> e o argumento se refere ao valor da visualização atual selecionada do Spinner. Ele retorna um matcher, que corresponde ao spinner com base no valor toString do item selecionado. O código de amostra é o seguinte,
onView(withSpinnerText(endsWith("USA")))
Ele aceita argumento de string ou id de recurso da string também. O código de amostra é o seguinte,
onView(withResourceName("USA"))
onView(withResourceName(R.string.res_usa))
withSubstring ()
withSubString () é semelhante a withText (), exceto que ajuda a testar a substring do valor de texto da visualização.
onView(withSubString("Hello"))
hasLinks ()
hasLinks () não tem argumentos e retorna um matcher, que corresponde à visão com links. Aplica-se apenas a TextView. O código de amostra é o seguinte,
onView(allOf(withSubString("Hello"), hasLinks()))
Aqui, allOf é um matcher Hamcrest. allOf retorna um matcher, que corresponde a todos os matchers passados e aqui, é usado para corresponder a uma visão, bem como verificar se a visão tem links em seu valor de texto.
hasTextColor ()
hasTextColor () aceita um único argumento do tipo int e o argumento se refere ao id de recurso da cor. Ele retorna um matcher, que corresponde ao TextView com base em sua cor. Aplica-se apenas a TextView . O código de amostra é o seguinte,
onView(allOf(withSubString("Hello"), hasTextColor(R.color.Red)))
hasEllipsizedText ()
hasEllipsizedText () não tem argumento. Ele retorna um matcher, que corresponde ao TextView que tem texto longo e reticulado (primeiro .. dez .. último) ou cortado (primeiro ...). O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_text_view_id), hasEllipsizedText()))
hasMultilineText ()
hasMultilineText () não tem argumento. Ele retorna um matcher, que corresponde ao TextView que possui qualquer texto de várias linhas. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_test_view_id), hasMultilineText()))
hasBackground ()
hasBackground () aceita um único argumento do tipo int e o argumento se refere ao id de recurso do recurso de fundo. Ele retorna um matcher, que corresponde à visualização com base em seus recursos de fundo. O código de amostra é o seguinte,
onView(allOf(withId("image"), hasBackground(R.drawable.your_drawable)))
hasErrorText ()
hasErrorText () aceita um argumento do tipo Matcher <String> e o argumento refere-se ao valor da string de erro da visualização (EditText). Ele retorna um matcher, que corresponde à visualização usando a string de erro da visualização. Isso se aplica apenas ao EditText . O código de amostra é o seguinte,
onView(allOf(withId(R.id.editText_name), hasErrorText(is("name is required"))))
Ele também aceita argumentos de string. O código de amostra é o seguinte,
onView(allOf(withId(R.id.editText_name), hasErrorText("name is required")))
hasImeAction ()
hasImeAction () aceita um argumento do tipo Matcher <Integer> e o argumento refere-se aos métodos de entrada suportados pela visão (EditText). Ele retorna um matcher, que corresponde à visualização usando o método de entrada compatível da visualização. Isso se aplica apenas ao EditText . O código de amostra é o seguinte,
onView(allOf(withId(R.id.editText_name),
hasImeAction(is(EditorInfo.IME_ACTION_GO))))
Aqui, EditorInfo.IME_ACTION_GO está entre as opções de métodos de entrada. hasImeAction () aceita argumentos inteiros também. O código de amostra é o seguinte,
onView(allOf(withId(R.id.editText_name),
hasImeAction(EditorInfo.IME_ACTION_GO)))
supportInputMethods ()
supportInputMethods () não tem argumento. Ele retorna um matcher, que corresponde à visualização, se ele suportar métodos de entrada. O código de amostra é o seguinte,
onView(allOf(withId(R.id.editText_name), supportsInputMethods()))
isRoot ()
isRoot () não tem argumento. Ele retorna um matcher, que corresponde à visão raiz. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_root_id), isRoot()))
é exibido()
isDisplayed () não tem argumento. Ele retorna um matcher, que corresponde à visualização exibida atualmente. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isDisplayed()))
isDisplayingAtLeast ()
isDisplayingAtLeast () aceita um único argumento do tipo int. Ele retorna um matcher, que corresponde à visualização que é exibida no momento pelo menos a porcentagem especificada. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isDisplayingAtLeast(75)))
isCompletelyDisplayed ()
isCompletelyDisplayed () não tem argumento. Ele retorna um matcher, que corresponde à visualização que é exibida atualmente na tela. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isCompletelyDisplayed()))
está ativado()
isEnabled () não tem argumento. Ele retorna um matcher, que corresponde à visualização que está ativada. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isEnabled()))
isFocusable ()
isFocusable () não tem argumento. Ele retorna um matcher, que corresponde à visualização que possui a opção de foco. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isFocusable()))
hasFocus ()
hasFocus () não tem argumento. Ele retorna um matcher, que corresponde à vista que está focada no momento. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), hasFocus()))
isClickable ()
isClickable () não tem argumento. Ele retorna um matcher, que corresponde à visualização da opção de clique. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isClickable()))
é selecionado()
isSelected () não tem argumento. Ele retorna um matcher, que corresponde à visualização atualmente selecionada. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isSelected()))
está checado()
isChecked () não tem argumento. Ele retorna um matcher, que corresponde à visualização do tipo CompoundButton (ou subtipo dele) e está no estado verificado. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_view_id), isChecked()))
isNotChecked ()
isNotChecked () é exatamente o oposto de isChecked. O código de amostra é o * seguinte,
onView(allOf(withId(R.id.my_view_id), isNotChecked()))
isJavascriptEnabled ()
isJavascriptEnabled () não tem argumento. Ele retorna um matcher, que corresponde ao WebView que está avaliando o JavaScript. O código de amostra é o seguinte,
onView(allOf(withId(R.id.my_webview_id), isJavascriptEnabled()))
Com os pais()
withParent () aceita um argumento do tipo Matcher <View>. O argumento refere-se a uma visão. Ele retorna um matcher, que corresponde à visão que a visão especificada é visão pai. O código de amostra é o seguinte,
onView(allOf(withId(R.id.childView), withParent(withId(R.id.parentView))))
hasSibling ()
hasSibling () aceita um argumento do tipo Matcher> View <. O argumento refere-se a uma visão. Ele retorna um matcher, que corresponde à visão que a visão passada é uma de suas visões irmãs. O código de amostra é o seguinte,
onView(hasSibling(withId(R.id.siblingView)))
com criança()
withChild () aceita um argumento do tipo Matcher <View>. O argumento refere-se a uma visão. Ele retorna um matcher, que corresponde à visão que a visão passada é visão filha. O código de amostra é o seguinte,
onView(allOf(withId(R.id.parentView), withChild(withId(R.id.childView))))
hasChildCount ()
hasChildCount () aceita um argumento do tipo int. O argumento se refere à contagem de filhos de uma visualização. Ele retorna um matcher, que corresponde à visualização que possui exatamente o mesmo número de visualizações filho conforme especificado no argumento. O código de amostra é o seguinte,
onView(hasChildCount(4))
hasMinimumChildCount ()
hasMinimumChildCount () aceita um argumento do tipo int. O argumento se refere à contagem de filhos de uma visualização. Ele retorna um matcher, que corresponde à visualização que possui pelo menos o número de visualizações filho conforme especificado no argumento. O código de amostra é o seguinte,
onView(hasMinimumChildCount(4))
hasDescendant ()
hasDescendant () aceita um argumento do tipo Matcher <View>. O argumento refere-se a uma visão. Ele retorna um matcher, que corresponde à visão que a visão passada é uma das visões descendentes na hierarquia de visões. O código de amostra é o seguinte,
onView(hasDescendant(withId(R.id.descendantView)))
isDescendantOfA ()
isDescendantOfA () aceita um argumento do tipo Matcher <View>. O argumento refere-se a uma visão. Ele retorna um matcher, que corresponde à visão que a visão passada é uma das visões ancestrais na hierarquia de visões. O código de amostra é o seguinte,
onView(allOf(withId(R.id.myView), isDescendantOfA(withId(R.id.parentView))))
O Espresso oferece várias opções para criar nossos próprios matchers de visualização personalizados e é baseado nos matchers Hamcrest . O matcher personalizado é um conceito muito poderoso para estender a estrutura e também personalizar a estrutura ao nosso gosto. Algumas das vantagens de escrever matchers personalizados são as seguintes,
Para explorar o recurso exclusivo de nossas próprias visualizações personalizadas
O matcher customizado ajuda nos casos de teste baseados em AdapterView para combinar com os diferentes tipos de dados subjacentes.
Para simplificar os combinadores atuais, combinando recursos de vários combinadores
Podemos criar um novo matcher à medida que surge a procura e é bastante fácil. Vamos criar um novo matcher personalizado, que retorna um matcher para testar o id e o texto de um TextView .
O Espresso oferece as duas classes a seguir para escrever novos matchers -
TypeSafeMatcher
BoundedMatcher
Ambas as classes são semelhantes em natureza, exceto que o BoundedMatcher lida de forma transparente com a conversão do objeto para o tipo correto sem verificar manualmente o tipo correto. Criaremos um novo matcher, withIdAndText usando a classe BoundedMatcher . Vamos verificar as etapas para escrever novos matchers.
Adicione a dependência abaixo no arquivo app / build.gradle e sincronize-o.
dependencies {
implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Crie uma nova classe para incluir nossos matchers (métodos) e marque-a como final
public final class MyMatchers {
}
Declare um método estático dentro da nova classe com os argumentos necessários e defina Matcher <View> como tipo de retorno.
public final class MyMatchers {
@NonNull
public static Matcher<View> withIdAndText(final Matcher<Integer>
integerMatcher, final Matcher<String> stringMatcher) {
}
}
Crie um novo objeto BoundedMatcher (valor de retorno também) com a assinatura abaixo dentro do método estático,
public final class MyMatchers {
@NonNull
public static Matcher<View> withIdAndText(final Matcher<Integer>
integerMatcher, final Matcher<String> stringMatcher) {
return new BoundedMatcher<View, TextView>(TextView.class) {
};
}
}
Substituir describeTo e matchesSafely métodos na BoundedMatcher objecto. describeTo tem um único argumento do tipo Descrição sem tipo de retorno e é usado para obter informações de erro relacionadas aos matchers. MatchSafely tem um único argumento do tipo TextView com o tipo de retorno booleano e é usado para corresponder à visualização.
A versão final do código é a seguinte,
public final class MyMatchers {
@NonNull
public static Matcher<View> withIdAndText(final Matcher<Integer>
integerMatcher, final Matcher<String> stringMatcher) {
return new BoundedMatcher<View, TextView>(TextView.class) {
@Override
public void describeTo(final Description description) {
description.appendText("error text: ");
stringMatcher.describeTo(description);
integerMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(final TextView textView) {
return stringMatcher.matches(textView.getText().toString()) &&
integerMatcher.matches(textView.getId());
}
};
}
}
Por fim, podemos usar nosso mew matcher para escrever o caso de teste conforme apresentado abaixo,
@Test
public void view_customMatcher_isCorrect() {
onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
.check(matches(withText("Hello World!")));
}
Conforme discutido anteriormente, a asserção de visão é usada para afirmar que tanto a visão real (encontrada usando os correspondentes de visão) quanto as visões esperadas são as mesmas. Um exemplo de código é o seguinte,
onView(withId(R.id.my_view)) .check(matches(withText("Hello")))
Aqui,
onView () retorna o objeto ViewInteration correspondente à visualização combinada. ViewInteraction é usado para interagir com a visualização correspondente.
withId (R.id.my_view) retorna um matcher de visualização que corresponderá à visualização (real) com atributos de id iguais a my_view .
withText (“Hello”) também retorna um comparador de visualização que corresponderá à visualização (esperado) com atributos de texto iguais a Hello .
check é um método que aceita um argumento do tipo ViewAssertion e faz asserção usando passado no objeto ViewAssertion .
corresponde (withText (“Hello”)) retorna uma asserção de visão, que fará oreal jobde afirmar que a visão real (encontrada usando withId ) e a visão esperada (encontrada usando withText ) são uma e a mesma.
Vamos aprender alguns dos métodos fornecidos pela estrutura de teste do espresso para declarar objetos de exibição.
não existe()
Retorna uma asserção de visão, o que garante que o comparador de visão não encontre nenhuma visão correspondente.
onView(withText("Hello")) .check(doesNotExist());
Aqui, o caso de teste garante que não haja visualização com o texto Hello.
partidas()
Aceita um matcher de visão de destino e retorna uma asserção de visão, o que garante que o matcher de visão (real) existe e combina com a visão combinada pelo matcher de visão de destino.
onView(withId(R.id.textView_hello)) .check(matches(withText("Hello World!")));
Aqui, o caso de teste garante que a visualização com id, R.id.textView_hello exista e corresponda à visualização de destino com o texto Hello World!
isBottomAlignedWith ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está alinhado na parte inferior com o matcher de visualização de destino.
onView(withId(R.id.view)) .check(isBottomAlignedWith(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e esteja alinhada na parte inferior com a visualização com id, R.id.target_view .
isCompletelyAbove ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está posicionado completamente acima do matcher de visualização de destino.
onView(withId(R.id.view)) .check(isCompletelyAbove(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada completamente acima da visualização com id, R.id.target_view
isCompletelyBelow ()
Aceita um matcher de visão de destino e retorna uma asserção de visão, que garante que o matcher de visão (real) existe e está posicionado completamente abaixo do matcher de visão de destino.
onView(withId(R.id.view)) .check(isCompletelyBelow(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada completamente abaixo da visualização com id, R.id.target_view .
isCompletelyLeftOf ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está posicionado completamente à esquerda do matcher de visualização de destino.
onView(withId(R.id.view)) .check(isCompletelyLeftOf(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada completamente à esquerda da visualização com id, R.id.target_view
isCompletelyRightOf ()
Aceita um matcher de visão de destino e retorna uma asserção de visão, que garante que o matcher de visão (real) existe e está posicionado completamente à direita do matcher de visão de destino.
onView(withId(R.id.view)) .check(isCompletelyRightOf(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visão com id, R.id.view exista e seja posicionada completamente à direita da visão com id, R.id.target_view.
isLeftAlignedWith ()
Aceita um matcher de visão de destino e retorna uma asserção de visão, que garante que o matcher de visão (real) existe e é alinhado à esquerda com o matcher de visão de destino.
onView(withId(R.id.view)) .check(isLeftAlignedWith(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja alinhada à esquerda com a visualização com id, R.id.target_view
isPartiallyAbove ()
Aceita um comparador de visualização de destino e retorna uma asserção de visualização, que garante que o comparador de visualização (real) existe e está posicionado parcialmente acima do comparador de visualização de destino.
onView(withId(R.id.view)) .check(isPartiallyAbove(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada parcialmente acima da visualização com id, R.id.target_view
isPartiallyBelow ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está posicionado parcialmente abaixo do matcher de visualização de destino.
onView(withId(R.id.view)) .check(isPartiallyBelow(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada parcialmente abaixo da visualização com id, R.id.target_view .
isPartiallyLeftOf ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está posicionado parcialmente à esquerda do matcher de visualização de destino.
onView(withId(R.id.view)) .check(isPartiallyLeftOf(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada parcialmente à esquerda da visualização com id, R.id.target_view .
isPartiallyRightOf ()
Aceita um matcher de visão de destino e retorna uma asserção de visão, o que garante que o matcher de visão (real) existe e está posicionado parcialmente à direita do matcher de visão de destino
onView(withId(R.id.view)) .check(isPartiallyRightOf(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e seja posicionada parcialmente à direita da visualização com id, R.id.target_view .
isRightAlignedWith ()
Aceita um matcher de visão de destino e retorna uma asserção de visão, o que garante que o matcher de visão (real) existe e está alinhado à direita com o matcher de visão de destino.
onView(withId(R.id.view)) .check(isRightAlignedWith(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visão com id, R.id.view exista e esteja alinhada com a visão com id, R.id.target_view .
isTopAlignedWith ()
Aceita um matcher de visualização de destino e retorna uma asserção de visualização, que garante que o matcher de visualização (real) existe e está alinhado no topo com o matcher de visualização de destino.
onView(withId(R.id.view)) .check(isTopAlignedWith(withId(R.id.target_view)))
Aqui, o caso de teste garante que a visualização com id, R.id.view exista e esteja alinhada com a visualização com id, R.id.target_view
noEllipsizedText ()
Retorna uma declaração de visão, que garante que a hierarquia de visão não contenha visões de texto elipsizadas ou cortadas.
onView(withId(R.id.view)) .check(noEllipsizedText());
noMultilineButtons ()
Retorna uma declaração de visão, o que garante que a hierarquia de visão não contenha botões com várias linhas.
onView(withId(R.id.view)) .check(noMultilineButtons());
noOverlaps ()
Retorna uma declaração de visão, que garante que o objeto descendente atribuível a TextView ou ImageView não se sobreponha. Ele tem outra opção, que aceita um matcher de visão de destino e retorna uma asserção de visão, o que garante que a visão descendente correspondente à visão de destino não se sobreponha.
Conforme aprendido anteriormente, as ações de visualização automatizam todas as ações possíveis executadas pelos usuários em um aplicativo Android. Espresso onView e “onData” fornecem o método perform , que aceita ações de visualização e invoca / automatiza as ações de usuário correspondentes no ambiente de teste. Por exemplo, “click ()” é uma ação de visualização, que quando passada para o método onView ( R.id.myButton ) .perform (click ()) , irá disparar o evento click do botão (com id: “myButton” ) no ambiente de teste.
Neste capítulo, vamos aprender sobre as ações de visualização fornecidas pela estrutura de teste do espresso.
typeText ()
typeText () aceita um argumento (texto) do tipo String e retorna uma ação de visualização. A ação de visualização retornada digita o texto fornecido na visualização. Antes de colocar o texto, ele toca na visualização uma vez. O conteúdo pode ser colocado em uma posição arbitrária se já contiver texto.
onView(withId(R.id.text_view)).perform(typeText("Hello World!"))
typeTextIntoFocusedView ()
typeTextIntoFocusedView () é semelhante a typeText (), exceto que coloca o texto ao lado da posição do cursor na visualização.
onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))
replaceText ()
replaceText () é semelhante a typeText (), exceto que substitui o conteúdo da visualização.
onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))
Texto claro()
clearText () não tem argumentos e retorna uma ação de visualização, que limpará o texto na visualização.
onView(withId(R.id.text_view)).perform(clearText())
pressione a tecla()
pressKey () aceita o código da tecla (por exemplo, KeyEvent.KEYCODE_ENTER) e retorna uma ação de visualização, que pressionará a tecla correspondente ao código da tecla.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", pressKey(KeyEvent.KEYCODE_ENTER))
pressMenuKey ()
pressMenuKey () não tem argumentos e retorna uma ação de visualização, que pressiona a tecla de menu do hardware.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", pressKey(KeyEvent.KEYCODE_ENTER), pressMenuKey())
closeSoftKeyboard ()
closeSoftKeyboard () não tem argumentos e retorna uma ação de visualização, que fechará o teclado, se um estiver aberto.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", closeSoftKeyboard())
clique()
click () não tem argumentos e retorna uma ação de visualização, que invocará a ação de clique da visualização.
onView(withId(R.id.button)).perform(click())
Duplo click()
doubleClick () não tem argumentos e retorna uma ação de visualização, que invocará a ação de clique duplo da visualização.
onView(withId(R.id.button)).perform(doubleClick())
longClick ()
longClick () não tem argumentos e retorna uma ação de visualização, que invocará a ação de clique longo da visualização.
onView(withId(R.id.button)).perform(longClick())
pressBack ()
pressBack () não tem argumentos e retorna uma ação de visualização, que clica no botão Voltar.
onView(withId(R.id.button)).perform(pressBack())
pressBackUnconditionally ()
pressBackUnconditionally () não tem argumentos e retorna uma ação de visualização, que clica no botão Voltar e não lança uma exceção se a ação do botão Voltar sair do próprio aplicativo.
onView(withId(R.id.button)).perform(pressBack())
link aberto()
openLink () possui dois argumentos. O primeiro argumento (texto do link) é do tipo Matcher e se refere ao texto da marca âncora HTML. O segundo argumento (url) é do tipo Matcher e se refere ao url da tag âncora HTML. É aplicável apenas para TextView . Ele retorna uma ação de visualização, que coleta todas as marcas âncora HTML disponíveis no conteúdo da visualização de texto, encontra a marca âncora correspondente ao primeiro argumento (texto do link) e ao segundo argumento (url) e, finalmente, abre a url correspondente. Vamos considerar uma visualização de texto com o conteúdo como -
<a href="http://www.google.com/">copyright</a>
Em seguida, o link pode ser aberto e testado usando o caso de teste abaixo,
onView(withId(R.id.text_view)).perform(openLink(is("copyright"),
is(Uri.parse("http://www.google.com/"))))
Aqui, openLink obterá o conteúdo da visualização do texto, encontrará o link com direitos autorais como texto, www.google.com como url e abrirá a url em um navegador.
openLinkWithText ()
openLinkWithText () tem um argumento, que pode ser do tipo ** String * ou Matcher. É simplesmente um atalho para o método openLink *.
onView(withId(R.id.text_view)).perform(openLinkWithText("copyright"))
openLinkWithUri ()
openLinkWithUri () tem um argumento, que pode ser do tipo String ou Matcher. É simplesmente um curto corte para a OpenLink método *.
onView(withId(R.id.text_view)).perform(openLinkWithUri("http://www.google.com/"))
pressImeActionButton ()
pressImeActionButton () não tem argumentos e retorna uma ação de visualização, que executará a ação definida na configuração android: imeOptions . Por exemplo, se android: imeOptions for igual a actionNext, isso moverá o cursor para a próxima visualização possível de EditText na tela.
onView(withId(R.id.text_view)).perform(pressImeActionButton())
scrollTo ()
scrollTo () não tem argumentos e retorna uma ação de visualização, que irá rolar o scrollView correspondente na tela.
onView(withId(R.id.scrollView)).perform(scrollTo())
deslize para baixo()
swipeDown () não tem argumentos e retorna uma ação de visualização, que irá disparar a ação de deslizar para baixo na tela.
onView(withId(R.id.root)).perform(swipeDown())
deslize para cima()
swipeUp () não tem argumentos e retorna uma ação de visualização, que irá disparar a ação de deslizar para cima na tela.
onView(withId(R.id.root)).perform(swipeUp())
desliza para a direita()
swipeRight () não tem argumentos e retorna uma ação de visualização, que disparará a ação de deslizar para a direita na tela.
onView(withId(R.id.root)).perform(swipeRight())
deslize para a esquerda()
swipeLeft () não tem argumentos e retorna uma ação de visualização, que disparará a ação de deslizar para a esquerda na tela.
onView(withId(R.id.root)).perform(swipeLeft())
AdapterView é um tipo especial de visualização projetado especificamente para renderizar uma coleção de informações semelhantes, como lista de produtos e contatos do usuário obtidos de uma fonte de dados subjacente usando o Adapter . A fonte de dados pode ser uma lista simples para entradas de banco de dados complexas. Algumas das visualizações derivadas de AdapterView são ListView , GridView e Spinner .
AdapterView renderiza a interface com o usuário dinamicamente, dependendo da quantidade de dados disponíveis na fonte de dados subjacente. Além disso, AdapterView renderiza apenas os dados mínimos necessários, que podem ser renderizados na área visível disponível da tela. AdapterView faz isso para economizar memória e fazer com que a interface do usuário pareça suave, mesmo se os dados subjacentes forem grandes.
Após a análise, a natureza da arquitetura AdapterView torna a opção onView e seus correspondentes de visualização irrelevantes porque a visualização específica a ser testada pode não ser renderizada em primeiro lugar. Felizmente, o espresso fornece um método, onData ( ), que aceita matchers hamcrest (relevantes para o tipo de dados dos dados subjacentes) para combinar os dados subjacentes e retorna o objeto do tipo DataInteraction correspondente à visualização dos dados correspondidos. Um exemplo de código é o seguinte,
onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())
Aqui, onData () corresponde à entrada “Apple”, se estiver disponível nos dados subjacentes (lista de matriz) e retorna o objeto DataInteraction para interagir com a visualização combinada (TextView correspondente à entrada “Apple”).
Métodos
DataInteraction fornece os métodos abaixo para interagir com a visualização,
executar ()
Isso aceita ações de visualização e dispara as ações de visualização passadas.
onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())
Verifica()
Aceita asserções de visão e verifica as asserções de visão passadas.
onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
.check(matches(withText("Apple")))
inAdapterView ()
Isso aceita correspondências de visualização. Ele seleciona o AdapterView específico com base nos matchers de visualização passados e retorna o objeto DataInteraction para interagir com o AdapterView correspondente
onData(allOf())
.inAdapterView(withId(R.id.adapter_view))
.atPosition(5)
.perform(click())
atPosition ()
Isso aceita um argumento do tipo inteiro e se refere à posição do item nos dados subjacentes. Ele seleciona a visualização correspondente ao valor posicional passado dos dados e retorna o objeto DataInteraction para interagir com a visualização correspondente. Será útil, se soubermos a ordem correta dos dados subjacentes.
onData(allOf())
.inAdapterView(withId(R.id.adapter_view))
.atPosition(5)
.perform(click())
onChildView ()
Isso aceita correspondências de visualização e corresponde à visualização dentro da visualização filho específica. Por exemplo, podemos interagir com itens específicos como o botão Comprar em uma lista de produtos baseada em AdapterView .
onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
.onChildView(withId(R.id.buy_button))
.perform(click())
Escreva um aplicativo de amostra
Siga as etapas mostradas abaixo para escrever um aplicativo simples baseado em AdapterView e escrever um caso de teste usando o método onData () .
Inicie o Android Studio.
Crie um novo projeto conforme discutido anteriormente e nomeie-o, MyFruitApp .
Migre o aplicativo para a estrutura AndroidX usando Refactor → Migrar para o menu de opções AndroidX .
Remova o design padrão da atividade principal e adicione ListView . O conteúdo do activity_main.xml é o seguinte,
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
tools:context = ".MainActivity">
<ListView
android:id = "@+id/listView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</RelativeLayout>
Adicione um novo recurso de layout, item.xml , para especificar o modelo de item da exibição de lista. O conteúdo do item.xml é o seguinte,
<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
android:id = "@+id/name"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:padding = "8dp"
/>
Agora, crie um adaptador com array de frutas como dados subjacentes e configure-o para a exibição de lista. Isso precisa ser feito em onCreate () de MainActivity conforme especificado abaixo,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Find fruit list view
final ListView listView = (ListView) findViewById(R.id.listView);
// Initialize fruit data
String[] fruits = new String[]{
"Apple",
"Banana",
"Cherry",
"Dates",
"Elderberry",
"Fig",
"Grapes",
"Grapefruit",
"Guava",
"Jack fruit",
"Lemon",
"Mango",
"Orange",
"Papaya",
"Pears",
"Peaches",
"Pineapple",
"Plums",
"Raspberry",
"Strawberry",
"Watermelon"
};
// Create array list of fruits
final ArrayList<String> fruitList = new ArrayList<String>();
for (int i = 0; i < fruits.length; ++i) {
fruitList.add(fruits[i]);
}
// Create Array adapter
final ArrayAdapter adapter = new ArrayAdapter(this, R.layout.item, fruitList);
// Set adapter in list view
listView.setAdapter(adapter);
}
Agora, compile o código e execute o aplicativo. A captura de tela do aplicativo My Fruit é a seguinte,
Agora, abra o arquivo ExampleInstrumentedTest.java e adicione ActivityTestRule conforme especificado abaixo,
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
Além disso, certifique-se de que a configuração do teste seja feita em app / build.gradle -
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Adicione um novo caso de teste para testar a exibição de lista conforme abaixo,
@Test
public void listView_isCorrect() {
// check list view is visible
onView(withId(R.id.listView)).check(matches(isDisplayed()));
onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click());
onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
.check(matches(withText("Apple")));
// click a child item
onData(allOf())
.inAdapterView(withId(R.id.listView))
.atPosition(10)
.perform(click());
}
Por fim, execute o caso de teste usando o menu de contexto do Android Studio e verifique se todos os casos de teste estão funcionando.
WebView é uma visualização especial fornecida pelo Android para exibir páginas da web dentro do aplicativo. O WebView não fornece todos os recursos de um aplicativo de navegador completo, como o Chrome e o Firefox. No entanto, ele fornece controle completo sobre o conteúdo a ser mostrado e expõe todos os recursos do Android a serem chamados dentro das páginas da web. Ele habilita o WebView e fornece um ambiente especial onde a IU pode ser facilmente projetada usando tecnologia HTML e recursos nativos como câmera e discagem de contato. Este conjunto de recursos permite que um WebView forneça um novo tipo de aplicativo chamado aplicativo Híbrido , em que a IU é feita em HTML e a lógica de negócios é feita em JavaScript ou por meio de um terminal de API externo.
Normalmente, testar um WebView precisa ser um desafio porque ele usa tecnologia HTML para seus elementos de interface de usuário em vez de interfaces de usuário / visualizações nativas. O Espresso se destaca nessa área ao fornecer um novo conjunto de correspondências e asserções da Web, que é intencionalmente semelhante a correspondências de exibição nativas e afirmações de exibição. Ao mesmo tempo, ele fornece uma abordagem bem balanceada, incluindo um ambiente de teste baseado em tecnologia da web também.
O Espresso web é construído sobre a estrutura WebDriver Atom, que é usada para localizar e manipular elementos da web. Atom é semelhante a ações de visualização. O Atom fará toda a interação dentro de uma página da web. O WebDriver expõe um conjunto predefinido de métodos, como findElement () , getElement () para localizar elementos da web e retorna os átomos correspondentes (para realizar ações na página da web).
Uma declaração de teste da web padrão se parece com o código abaixo,
onWebView()
.withElement(Atom)
.perform(Atom)
.check(WebAssertion)
Aqui,
onWebView () - Semelhante a onView (), expõe um conjunto de API para testar um WebView.
withElement () - Um dos vários métodos usados para localizar elementos da web dentro de uma página da web usando Atom e retorna o objeto WebInteration, que é semelhante a ViewInteraction.
perform () - executa a ação dentro de uma página da web usando Atom e retorna WebInteraction.
check () - faz a declaração necessária usando WebAssertion.
Um exemplo de código de teste da web é o seguinte,
onWebView()
.withElement(findElement(Locator.ID, "apple"))
.check(webMatches(getText(), containsString("Apple")))
Aqui,
findElement () localiza um elemento e retorna um Atom
webMatches é semelhante ao método de correspondências
Escreva um aplicativo de amostra
Vamos escrever um aplicativo simples baseado em WebView e escrever um caso de teste usando o método onWebView () . Siga estas etapas para escrever um aplicativo de amostra -
Inicie o Android Studio.
Crie um novo projeto conforme discutido anteriormente e denomine -o MyWebViewApp .
Migre o aplicativo para a estrutura AndroidX usando Refactor → Migrar para o menu de opções AndroidX .
Adicione a opção de configuração abaixo no arquivo AndroidManifest.xml para dar permissão para acessar a Internet.
<uses-permission android:name = "android.permission.INTERNET" />
O Espresso Web é fornecido como um plugin separado. Portanto, adicione a dependência no app / build.gradle e sincronize-a.
dependencies {
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.1'
}
Remova o design padrão da atividade principal e adicione WebView. O conteúdo do activity_main.xml é o seguinte,
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
tools:context = ".MainActivity">
<WebView
android:id = "@+id/web_view_test"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent" />
</RelativeLayout>
Crie uma nova classe, ExtendedWebViewClient estendendo WebViewClient e substitua o método shouldOverrideUrlLoading para carregar a ação do link no mesmo WebView ; caso contrário, ele abrirá uma nova janela do navegador fora do aplicativo. Coloque-o em MainActivity.java .
private class ExtendedWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
Agora, adicione o código abaixo no método onCreate de MainActivity . O objetivo do código é encontrar o WebView , configurá-lo corretamente e, finalmente, carregar o url de destino.
// Find web view
WebView webView = (WebView) findViewById(R.id.web_view_test);
// set web view client
webView.setWebViewClient(new ExtendedWebViewClient());
// Clear cache
webView.clearCache(true);
// load Url
webView.loadUrl("http://<your domain or IP>/index.html");
Aqui,
O conteúdo de index.html é o seguinte -
<html>
<head>
<title>Android Web View Sample</title>
</head>
<body>
<h1>Fruits</h1>
<ol>
<li><a href = "apple.html" id = "apple">Apple</a></li>
<li><a href = "banana.html" id = "banana">Banana</a></li>
</ol>
</body>
</html>
O conteúdo do arquivo apple.html referido em index.html é o seguinte -
<html>
<head>
<title>Android Web View Sample</title>
</head>
<body>
<h1>Apple</h1>
</body>
</html>
O conteúdo do arquivo banana.html referido em banana.html é o seguinte,
<html>
<head>
<title>Android Web View Sample</title>
</head>
<body>
<h1>Banana</h1>
</body>
</html>
Coloque index.html, apple.html e banana.html em um servidor da web
Substitua o url no método loadUrl pelo seu url configurado.
Agora, execute o aplicativo e verifique manualmente se está tudo bem. Abaixo está a captura de tela do aplicativo de amostra WebView -
Agora, abra o arquivo ExampleInstrumentedTest.java e adicione a regra abaixo -
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class, false, true) {
@Override
protected void afterActivityLaunched() {
onWebView(withId(R.id.web_view_test)).forceJavascriptEnabled();
}
};
Aqui, encontramos o WebView e o JavaScript ativado do WebView porque a estrutura de teste da web do espresso funciona exclusivamente por meio do mecanismo JavaScript para identificar e manipular o elemento da web.
Agora, adicione o caso de teste para testar nosso WebView e seu comportamento.
@Test
public void webViewTest(){
onWebView()
.withElement(findElement(Locator.ID, "apple"))
.check(webMatches(getText(), containsString("Apple")))
.perform(webClick())
.withElement(findElement(Locator.TAG_NAME, "h1"))
.check(webMatches(getText(), containsString("Apple")));
}
Aqui, o teste foi feito na seguinte ordem,
encontrou o link, apple usando seu atributo id por meio do método findElement () e enumeração Locator.ID .
verifica o texto do link usando o método webMatches ()
executa uma ação de clique no link. Ele abre a página apple.html .
novamente encontrou o elemento h1 usando métodos findElement () e enumeração Locator.TAG_NAME .
finalmente, verifica novamente o texto da tag h1 usando o método webMatches () .
Finalmente, execute o caso de teste usando o menu de contexto do Android Studio.
Neste capítulo, aprenderemos como testar operações assíncronas usando Espresso Idling Resources.
Um dos desafios do aplicativo moderno é fornecer uma experiência tranquila ao usuário. Fornecer uma experiência de usuário tranquila envolve muito trabalho em segundo plano para garantir que o processo do aplicativo não leve mais do que alguns milissegundos. A tarefa em segundo plano varia de uma tarefa simples a uma tarefa cara e complexa de buscar dados de API / banco de dados remoto. Para enfrentar o desafio do passado, um desenvolvedor costumava escrever tarefas caras e de longa execução em um thread de segundo plano e sincronizar com o UIThread principal quando o thread de segundo plano era concluído.
Se desenvolver um aplicativo multithread é complexo, escrever casos de teste para ele é ainda mais complexo. Por exemplo, não devemos testar um AdapterView antes de os dados necessários serem carregados do banco de dados. Se a busca dos dados for feita em um thread separado, o teste precisa esperar até que o thread seja concluído. Portanto, o ambiente de teste deve ser sincronizado entre o thread de segundo plano e o thread de IU. O Espresso fornece um excelente suporte para testar o aplicativo multithread. Um aplicativo usa thread das seguintes maneiras e o espresso oferece suporte a todos os cenários.
Threading da interface do usuário
Ele é usado internamente pelo Android SDK para fornecer uma experiência de usuário tranquila com elementos complexos da IU. O Espresso oferece suporte a esse cenário de maneira transparente e não precisa de nenhuma configuração e codificação especial.
Tarefa assíncrona
As linguagens de programação modernas oferecem suporte à programação assíncrona para fazer threads leves sem a complexidade da programação de threads. A tarefa assíncrona também é suportada de forma transparente pela estrutura espresso.
Tópico do usuário
Um desenvolvedor pode iniciar um novo thread para buscar dados complexos ou grandes do banco de dados. Para dar suporte a esse cenário, o espresso fornece o conceito de recurso inativo.
Vamos aprender o conceito de recurso ocioso e como fazê-lo neste capítulo.
Visão geral
O conceito de recurso ocioso é muito simples e intuitivo. A ideia básica é criar uma variável (valor booleano) sempre que um processo de longa execução for iniciado em uma thread separada para identificar se o processo está em execução ou não e registrá-lo no ambiente de teste. Durante o teste, o executor de teste verificará a variável registrada, se houver alguma encontrada, e então encontrará seu status de execução. Se o status de execução for verdadeiro, o executor de teste aguardará até que o status se torne falso.
O Espresso fornece uma interface, IdlingResources, com o objetivo de manter o status de execução. O principal método de implementação é isIdleNow (). Se isIdleNow () retornar verdadeiro, o espresso irá retomar o processo de teste ou então esperará até que isIdleNow () retorne falso. Precisamos implementar IdlingResources e usar a classe derivada. O Espresso também fornece algumas das implementações internas de IdlingResources para facilitar nossa carga de trabalho. Eles são os seguintes,
CountingIdlingResource
Isso mantém um contador interno de tarefas em execução. Ele expõe os métodos increment () e decrement () . increment () adiciona um ao contador e decrement () remove um do contador. isIdleNow () retorna verdadeiro somente quando nenhuma tarefa está ativa.
UriIdlingResource
Isso é semelhante a CounintIdlingResource, exceto que o contador precisa ser zero por um longo período para obter a latência da rede também.
IdlingThreadPoolExecutor
Esta é uma implementação personalizada de ThreadPoolExecutor para manter o número de tarefas em execução ativas no pool de threads atual.
IdlingScheduledThreadPoolExecutor
Isso é semelhante a IdlingThreadPoolExecutor , mas também agenda uma tarefa e uma implementação personalizada de ScheduledThreadPoolExecutor.
Se qualquer uma das implementações de IdlingResources acima ou um customizado for usado no aplicativo, precisamos registrá-lo no ambiente de teste também antes de testar o aplicativo usando a classe IdlingRegistry conforme abaixo,
IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
Além disso, ele pode ser removido assim que o teste for concluído conforme abaixo -
IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
O Espresso fornece essa funcionalidade em um pacote separado, e o pacote precisa ser configurado conforme abaixo no app.gradle.
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Aplicativo de amostra
Vamos criar um aplicativo simples para listar as frutas obtendo-as de um serviço da web em um thread separado e, em seguida, testá-lo usando o conceito de recurso inativo.
Inicie o Android Studio.
Crie um novo projeto conforme discutido anteriormente e nomeie-o, MyIdlingFruitApp
Migre o aplicativo para a estrutura AndroidX usando Refactor → Migrar para o menu de opções AndroidX .
Adicione a biblioteca de recursos de inatividade do espresso no app / build.gradle (e sincronize-o) conforme especificado abaixo,
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Remova o design padrão na atividade principal e adicione ListView. O conteúdo do activity_main.xml é o seguinte,
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
tools:context = ".MainActivity">
<ListView
android:id = "@+id/listView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</RelativeLayout>
Adicione um novo recurso de layout, item.xml , para especificar o modelo de item da exibição de lista. O conteúdo do item.xml é o seguinte,
<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
android:id = "@+id/name"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:padding = "8dp"
/>
Crie uma nova classe - MyIdlingResource . MyIdlingResource é usado para manter nosso IdlingResource em um lugar e buscá-lo sempre que necessário. Vamos usar CountingIdlingResource em nosso exemplo.
package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;
public class MyIdlingResource {
private static CountingIdlingResource mCountingIdlingResource =
new CountingIdlingResource("my_idling_resource");
public static void increment() {
mCountingIdlingResource.increment();
}
public static void decrement() {
mCountingIdlingResource.decrement();
}
public static IdlingResource getIdlingResource() {
return mCountingIdlingResource;
}
}
Declare uma variável global, mIdlingResource do tipo CountingIdlingResource na classe MainActivity conforme abaixo,
@Nullable
private CountingIdlingResource mIdlingResource = null;
Escreva um método privado para buscar uma lista de frutas da web como abaixo,
private ArrayList<String> getFruitList(String data) {
ArrayList<String> fruits = new ArrayList<String>();
try {
// Get url from async task and set it into a local variable
URL url = new URL(data);
Log.e("URL", url.toString());
// Create new HTTP connection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set HTTP connection method as "Get"
conn.setRequestMethod("GET");
// Do a http request and get the response code
int responseCode = conn.getResponseCode();
// check the response code and if success, get response content
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuffer response = new StringBuffer();
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
JSONArray jsonArray = new JSONArray(response.toString());
Log.e("HTTPResponse", response.toString());
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = String.valueOf(jsonObject.getString("name"));
fruits.add(name);
}
} else {
throw new IOException("Unable to fetch data from url");
}
conn.disconnect();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return fruits;
}
Crie uma nova tarefa no método onCreate () para buscar os dados da web usando nosso método getFruitList seguido pela criação de um novo adaptador e configurando-o para exibição de lista. Além disso, diminua o recurso inativo assim que nosso trabalho for concluído no thread. O código é o seguinte,
// Get data
class FruitTask implements Runnable {
ListView listView;
CountingIdlingResource idlingResource;
FruitTask(CountingIdlingResource idlingRes, ListView listView) {
this.listView = listView;
this.idlingResource = idlingRes;
}
public void run() {
//code to do the HTTP request
final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
try {
synchronized (this){
runOnUiThread(new Runnable() {
@Override
public void run() {
// Create adapter and set it to list view
final ArrayAdapter adapter = new
ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
ListView listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
MyIdlingResource.decrement(); // Set app as idle.
}
}
}
Aqui, o URL da fruta é considerado como http: // <seu domínio ou IP / fruits.json e é formatado como JSON. O conteúdo é o seguinte,
[
{
"name":"Apple"
},
{
"name":"Banana"
},
{
"name":"Cherry"
},
{
"name":"Dates"
},
{
"name":"Elderberry"
},
{
"name":"Fig"
},
{
"name":"Grapes"
},
{
"name":"Grapefruit"
},
{
"name":"Guava"
},
{
"name":"Jack fruit"
},
{
"name":"Lemon"
},
{
"name":"Mango"
},
{
"name":"Orange"
},
{
"name":"Papaya"
},
{
"name":"Pears"
},
{
"name":"Peaches"
},
{
"name":"Pineapple"
},
{
"name":"Plums"
},
{
"name":"Raspberry"
},
{
"name":"Strawberry"
},
{
"name":"Watermelon"
}
]
Note - Coloque o arquivo em seu servidor web local e use-o.
Agora, encontre a visualização, crie um novo encadeamento passando FruitTask , incremente o recurso inativo e, finalmente, inicie a tarefa.
// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
O código completo de MainActivity é o seguinte,
package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.test.espresso.idling.CountingIdlingResource;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
@Nullable
private CountingIdlingResource mIdlingResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get data
class FruitTask implements Runnable {
ListView listView;
CountingIdlingResource idlingResource;
FruitTask(CountingIdlingResource idlingRes, ListView listView) {
this.listView = listView;
this.idlingResource = idlingRes;
}
public void run() {
//code to do the HTTP request
final ArrayList<String> fruitList = getFruitList(
"http://<yourdomain or IP>/fruits.json");
try {
synchronized (this){
runOnUiThread(new Runnable() {
@Override
public void run() {
// Create adapter and set it to list view
final ArrayAdapter adapter = new ArrayAdapter(
MainActivity.this, R.layout.item, fruitList);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
MyIdlingResource.decrement(); // Set app as idle.
}
}
}
// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
}
private ArrayList<String> getFruitList(String data) {
ArrayList<String> fruits = new ArrayList<String>();
try {
// Get url from async task and set it into a local variable
URL url = new URL(data);
Log.e("URL", url.toString());
// Create new HTTP connection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set HTTP connection method as "Get"
conn.setRequestMethod("GET");
// Do a http request and get the response code
int responseCode = conn.getResponseCode();
// check the response code and if success, get response content
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuffer response = new StringBuffer();
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
JSONArray jsonArray = new JSONArray(response.toString());
Log.e("HTTPResponse", response.toString());
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = String.valueOf(jsonObject.getString("name"));
fruits.add(name);
}
} else {
throw new IOException("Unable to fetch data from url");
}
conn.disconnect();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return fruits;
}
}
Agora, adicione a configuração abaixo no arquivo de manifesto do aplicativo, AndroidManifest.xml
<uses-permission android:name = "android.permission.INTERNET" />
Agora, compile o código acima e execute o aplicativo. A captura de tela do aplicativo My Idling Fruit é a seguinte,
Agora, abra o arquivo ExampleInstrumentedTest.java e adicione ActivityTestRule conforme especificado abaixo,
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
Also, make sure the test configuration is done in app/build.gradle
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Adicione um novo caso de teste para testar a exibição de lista conforme abaixo,
@Before
public void registerIdlingResource() {
IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
}
@Test
public void contentTest() {
// click a child item
onData(allOf())
.inAdapterView(withId(R.id.listView))
.atPosition(10)
.perform(click());
}
@After
public void unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
}
Por fim, execute o caso de teste usando o menu de contexto do Android Studio e verifique se todos os casos de teste estão funcionando.
O Android Intent é usado para abrir uma nova atividade, seja interna (abrir uma tela de detalhes do produto na tela da lista de produtos) ou externa (como abrir um discador para fazer uma chamada). A atividade de intenção interna é tratada de forma transparente pela estrutura de teste do espresso e não precisa de nenhum trabalho específico do lado do usuário. No entanto, invocar a atividade externa é realmente um desafio porque está fora do nosso escopo, o aplicativo em teste. Depois que o usuário invoca um aplicativo externo e sai do aplicativo em teste, as chances de o usuário voltar ao aplicativo com uma sequência de ação predefinida são menores. Portanto, precisamos assumir a ação do usuário antes de testar o aplicativo. O Espresso oferece duas opções para lidar com essa situação. Eles são os seguintes,
pretendido
Isso permite que o usuário verifique se o intent correto é aberto no aplicativo em teste.
pretendendo
Isso permite ao usuário simular uma atividade externa, como tirar uma foto da câmera, discar um número da lista de contatos, etc., e retornar ao aplicativo com um conjunto predefinido de valores (como uma imagem predefinida da câmera em vez da imagem real) .
Configuração
O Espresso oferece suporte à opção de intenção por meio de uma biblioteca de plug-ins e a biblioteca precisa ser configurada no arquivo gradle do aplicativo. A opção de configuração é a seguinte,
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
pretendido()
O plug-in de intent do Espresso fornece correspondências especiais para verificar se a intent invocada é a esperada. Os matchers fornecidos e a finalidade dos matchers são os seguintes,
hasAction
Isso aceita a ação do intent e retorna um matcher, que corresponde ao intent especificado.
hasData
Isso aceita os dados e retorna um matcher, que corresponde aos dados fornecidos para o intent ao invocá-lo.
embalar
Isso aceita o nome do pacote do intent e retorna um matcher, que corresponde ao nome do pacote do intent invocado.
Agora, vamos criar um novo aplicativo e testá-lo quanto à atividade externa usando intend () para entender o conceito.
Inicie o Android Studio.
Crie um novo projeto conforme discutido anteriormente e nomeie-o IntentSampleApp.
Migre o aplicativo para a estrutura AndroidX usando Refactor → Migrar para o menu de opções AndroidX .
Crie uma caixa de texto, um botão para abrir a lista de contatos e outro para fazer uma chamada, alterando o activity_main.xml como mostrado abaixo,
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
tools:context = ".MainActivity">
<EditText
android:id = "@+id/edit_text_phone_number"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:text = ""
android:autofillHints = "@string/phone_number"/>
<Button
android:id = "@+id/call_contact_button"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_below = "@id/edit_text_phone_number"
android:text = "@string/call_contact"/>
<Button
android:id = "@+id/button"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_centerHorizontal = "true"
android:layout_below = "@id/call_contact_button"
android:text = "@string/call"/>
</RelativeLayout>
Além disso, adicione o item abaixo no arquivo de recurso strings.xml ,
<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
Agora, adicione o código abaixo na atividade principal ( MainActivity.java ) sob o método onCreate .
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// ... code
// Find call from contact button
Button contactButton = (Button) findViewById(R.id.call_contact_button);
contactButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Uri uri = Uri.parse("content://contacts");
Intent contactIntent = new Intent(Intent.ACTION_PICK,
ContactsContract.Contacts.CONTENT_URI);
contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
startActivityForResult(contactIntent, REQUEST_CODE);
}
});
// Find edit view
final EditText phoneNumberEditView = (EditText)
findViewById(R.id.edit_text_phone_number);
// Find call button
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(phoneNumberEditView.getText() != null) {
Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
startActivity(callIntent);
}
}
});
}
// ... code
}
Aqui, programamos o botão com id, call_contact_button para abrir a lista de contatos e o botão com id, botão para discar a chamada.
Adicione uma variável estática REQUEST_CODE na classe MainActivity como mostrado abaixo,
public class MainActivity extends AppCompatActivity {
// ...
private static final int REQUEST_CODE = 1;
// ...
}
Agora, adicione o método onActivityResult na classe MainActivity como abaixo,
public class MainActivity extends AppCompatActivity {
// ...
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == RESULT_OK) {
// Bundle extras = data.getExtras();
// String phoneNumber = extras.get("data").toString();
Uri uri = data.getData();
Log.e("ACT_RES", uri.toString());
String[] projection = {
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
cursor.moveToFirst();
int numberColumnIndex =
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String number = cursor.getString(numberColumnIndex);
int nameColumnIndex = cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
String name = cursor.getString(nameColumnIndex);
Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);
// Find edit view
final EditText phoneNumberEditView = (EditText)
findViewById(R.id.edit_text_phone_number);
phoneNumberEditView.setText(number);
}
}
};
// ...
}
Aqui, onActivityResult será chamado quando um usuário retornar ao aplicativo após abrir a lista de contatos usando o botão call_contact_button e selecionar um contato. Depois que o método onActivityResult é invocado, ele obtém o contato selecionado pelo usuário, localiza o número do contato e o define na caixa de texto.
Execute o aplicativo e verifique se está tudo bem. A aparência final do aplicativo de amostra Intent é mostrado abaixo,
Agora, configure a intenção do espresso no arquivo gradle do aplicativo, conforme mostrado abaixo,
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
Clique na opção de menu Sincronizar agora fornecida pelo Android Studio. Isso fará o download da biblioteca de teste de intenção e a configurará corretamente.
Abra o arquivo ExampleInstrumentedTest.java e adicione o IntentsTestRule em vez do AndroidTestRule normalmente usado . IntentTestRule é uma regra especial para lidar com testes de intenção.
public class ExampleInstrumentedTest {
// ... code
@Rule
public IntentsTestRule<MainActivity> mActivityRule =
new IntentsTestRule<>(MainActivity.class);
// ... code
}
Adicione duas variáveis locais para definir o número de telefone de teste e o nome do pacote do discador conforme abaixo,
public class ExampleInstrumentedTest {
// ... code
private static final String PHONE_NUMBER = "1 234-567-890";
private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
// ... code
}
Corrija os problemas de importação usando a opção Alt + Enter fornecida pelo android studio ou inclua as instruções de importação abaixo,
import android.content.Context;
import android.content.Intent;
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
Adicione o caso de teste abaixo para testar se o discador é chamado corretamente,
public class ExampleInstrumentedTest {
// ... code
@Test
public void validateIntentTest() {
onView(withId(R.id.edit_text_phone_number))
.perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
onView(withId(R.id.button)) .perform(click());
intended(allOf(
hasAction(Intent.ACTION_DIAL),
hasData("tel:" + PHONE_NUMBER),
toPackage(DIALER_PACKAGE_NAME)));
}
// ... code
}
Aqui, os matchers hasAction , hasData e toPackage são usados junto com o matcher allOf para ter sucesso apenas se todos os matchers forem passados.
Agora, execute o ExampleInstrumentedTest por meio do menu de conteúdo no Android Studio.
pretendendo ()
O Espresso fornece um método especial - intending () para simular uma ação de intenção externa. intending () aceita o nome do pacote da intenção a ser simulada e fornece um método respondWith para definir como a intenção simulada precisa ser respondida conforme especificado abaixo,
intending(toPackage("com.android.contacts")).respondWith(result);
Aqui, respondWith () aceita o resultado da intenção do tipo Instrumentation.ActivityResult . Podemos criar uma nova intent de stub e definir manualmente o resultado conforme especificado abaixo,
// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
O código completo para testar se um aplicativo de contato está aberto corretamente é o seguinte,
@Test
public void stubIntentTest() {
// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
intending(toPackage("com.android.contacts")).respondWith(result);
// find the button and perform click action
onView(withId(R.id.call_contact_button)).perform(click());
// get context
Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();
// get phone number
String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
Cursor cursor =
targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
acts/data/1"), projection, null, null, null);
cursor.moveToFirst();
int numberColumnIndex =
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
String number = cursor.getString(numberColumnIndex);
// now, check the data
onView(withId(R.id.edit_text_phone_number))
.check(matches(withText(number)));
}
Aqui, criamos um novo intent e definimos o valor de retorno (ao invocar o intent) como a primeira entrada da lista de contatos, content: //com.android.contacts/data/1 . Em seguida, definimos o método de intenção para simular a intenção recém-criada no lugar da lista de contatos. Ele define e chama nosso intent recém-criado quando o pacote, com.android.contacts , é invocado e a primeira entrada padrão da lista é retornada. Em seguida, disparamos a ação click () para iniciar o intent simulado e, por fim, verificamos se o número de telefone da chamada do intent simulado e o número da primeira entrada na lista de contatos são iguais.
Se houver algum problema de importação ausente, corrija esses problemas de importação usando a opção Alt + Enter fornecida pelo android studio ou inclua as instruções de importação abaixo,
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
Adicione a regra abaixo na classe de teste para fornecer permissão para ler a lista de contatos -
@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);
Adicione a opção abaixo no arquivo de manifesto do aplicativo, AndroidManifest.xml -
<uses-permission android:name = "android.permission.READ_CONTACTS" />
Agora, certifique-se de que a lista de contatos tenha pelo menos uma entrada e execute o teste usando o menu de contexto do Android Studio.
O Android oferece suporte a testes de interface de usuário que envolvem mais de um aplicativo. Vamos considerar que nosso aplicativo tem uma opção de passar de nosso aplicativo para aplicativo de mensagens para enviar uma mensagem e depois voltar para nosso aplicativo. Nesse cenário, a estrutura de teste do automatizador de IU nos ajuda a testar o aplicativo. O automatizador de IU pode ser considerado um bom companheiro para a estrutura de teste do espresso. Podemos explorar a opção intending () na estrutura de teste do espresso antes de optar pelo automatizador de IU .
Instrução de configuração
O Android fornece o automatizador de IU como um plug-in separado. Ele precisa ser configurado no app / build.gradle conforme especificado abaixo,
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Fluxo de trabalho para escrever casos de teste
Vamos entender como escrever um caso de teste baseado no UI Automator ,
Obtenha o objeto UiDevice chamando o método getInstance () e passando o objeto Instrumentation .
myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
Obtenha o objeto UiObject usando o método findObject () . Antes de usar este método, podemos abrir o aplicativo uiautomatorviewer para inspecionar os componentes de IU do aplicativo de destino, pois compreender o aplicativo de destino nos permite escrever casos de teste melhores.
UiObject button = myDevice.findObject(new UiSelector()
.text("Run")
.className("android.widget.Button"));
Simule a interação do usuário chamando o método UiObject . Por exemplo, setText () para editar um campo de texto e click () para disparar um evento click de um botão.
if(button.exists() && button.isEnabled()) {
button.click();
}
Por fim, verificamos se a IU reflete o estado esperado.
Escrever casos de teste é um trabalho tedioso. Mesmo que o espresso forneça uma API muito fácil e flexível, escrever casos de teste pode ser uma tarefa preguiçosa e demorada. Para superar isso, o Android Studio fornece um recurso para gravar e gerar casos de teste do espresso. O registro do teste Espresso está disponível no menu Executar .
Vamos registrar um caso de teste simples em nosso HelloWorldApp seguindo as etapas descritas abaixo,
Abra o Android Studio seguido pelo aplicativo HelloWorldApp .
Clique em Executar → Gravar teste Espresso e selecione MainActivity .
A captura de tela do gravador é a seguinte,
Clique em Adicionar asserção . Ele abrirá a tela do aplicativo conforme mostrado abaixo,
Clique em Hello World! . A tela do gravador para selecionar a visualização de texto é a seguinte,
Clique novamente em Salvar declaração. Isso salvará a declaração e mostrará como segue,
Clique OK . Será aberta uma nova janela e perguntará o nome do caso de teste. O nome padrão é MainActivityTest
Altere o nome do caso de teste, se necessário.
Novamente, clique em OK . Isso irá gerar um arquivo, MainActivityTest , com nosso caso de teste registrado. A codificação completa é a seguinte,
package com.tutorialspoint.espressosamples.helloworldapp;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.espresso.ViewInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void mainActivityTest() {
ViewInteraction textView = onView(
allOf(withId(R.id.textView_hello), withText("Hello World!"),
childAtPosition(childAtPosition(withId(android.R.id.content),
0),0),isDisplayed()));
textView.check(matches(withText("Hello World!")));
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup &&
parentMatcher.matches(parent)&& view.equals(((ViewGroup)
parent).getChildAt(position));
}
};
}
}
Finalmente, execute o teste usando o menu de contexto e verifique se o caso de teste foi executado.
A experiência positiva do usuário desempenha um papel muito importante no sucesso de um aplicativo. A experiência do usuário não envolve apenas lindas interfaces de usuário, mas também a rapidez com que essas lindas interfaces de usuário são renderizadas e qual é a taxa de quadros por segundo. A interface do usuário precisa ser executada de forma consistente a 60 quadros por segundo para oferecer uma boa experiência ao usuário.
Vamos aprender algumas das opções disponíveis no Android para analisar o desempenho da IU neste capítulo.
lixo
dumpsys é uma ferramenta embutida disponível no dispositivo Android. Ele produz informações atuais sobre os serviços do sistema. dumpsys tem a opção de despejar informações sobre uma categoria específica. Passar gfxinfo fornecerá informações de animação do pacote fornecido. O comando é o seguinte,
> adb shell dumpsys gfxinfo <PACKAGE_NAME>
framestats
framestats é uma opção do comando dumpsys. Depois que o dumpsys é invocado com framestats , ele despeja informações detalhadas de tempo de quadro dos quadros recentes. O comando é o seguinte,
> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats
Ele produz as informações como CSV (valores separados por vírgula). A saída em formato CSV ajuda a enviar facilmente os dados para o Excel e, posteriormente, extrair informações úteis por meio de fórmulas e gráficos do Excel.
systrace
systrace também é uma ferramenta integrada disponível no dispositivo Android. Ele captura e exibe os tempos de execução dos processos do aplicativo. o systrace pode ser executado usando o comando abaixo no terminal do Android Studio,
python %ANDROID_HOME%/platform-tools/systrace/systrace.py --time=10 -o
my_trace_output.html gfx view res
O recurso de acessibilidade é um dos principais recursos de qualquer aplicativo. O aplicativo desenvolvido por um fornecedor deve oferecer suporte à diretriz de acessibilidade mínima definida pelo Android SDK para ser um aplicativo útil e bem-sucedido. Seguir o padrão de acessibilidade é muito importante e não é uma tarefa fácil. O Android SDK fornece excelente suporte, fornecendo visualizações projetadas adequadamente para criar interfaces de usuário acessíveis.
Da mesma forma, a estrutura de teste do Espresso faz um grande favor tanto para o desenvolvedor quanto para o usuário final, oferecendo suporte transparente aos recursos de teste de acessibilidade no mecanismo de teste de núcleo.
No Espresso, um desenvolvedor pode habilitar e configurar o teste de acessibilidade por meio da classe AccessibilityChecks . O código de amostra é o seguinte,
AccessibilityChecks.enable();
Por padrão, as verificações de acessibilidade são executadas quando você executa qualquer ação de visualização. A verificação inclui a visualização na qual a ação é executada, bem como todas as visualizações descendentes. Você pode verificar toda a hierarquia de visualização de uma tela usando o seguinte código -
AccessibilityChecks.enable().setRunChecksFromRootView(true);
Conclusão
O Espresso é uma ótima ferramenta para desenvolvedores Android testarem seus aplicativos completamente de uma maneira muito fácil e sem colocar esforços extras normalmente exigidos por uma estrutura de teste. Tem até gravador para criar caso de teste sem escrever o código manualmente. Além disso, ele suporta todos os tipos de teste de interface do usuário. Ao usar a estrutura de teste do espresso, um desenvolvedor de Android pode desenvolver com segurança um aplicativo de ótima aparência, bem como um aplicativo de sucesso sem problemas em um curto período de tempo.