Marco de prueba de Espresso - Guía rápida
En general, las pruebas de automatización móvil son una tarea difícil y desafiante. La disponibilidad de Android para diferentes dispositivos y plataformas hace que las pruebas de automatización móvil sean tediosas. Para hacerlo más fácil, Google aceptó el desafío y desarrolló el marco de Espresso. Proporciona una API muy simple, consistente y flexible para automatizar y probar las interfaces de usuario en una aplicación de Android. Las pruebas de Espresso se pueden escribir tanto en Java como en Kotlin, un lenguaje de programación moderno para desarrollar aplicaciones de Android.
La API de Espresso es simple y fácil de aprender. Puede realizar fácilmente pruebas de IU de Android sin la complejidad de las pruebas de subprocesos múltiples. Google Drive, Maps y algunas otras aplicaciones utilizan actualmente Espresso.
Características de Espresso
Algunas de las características destacadas compatibles con Espresso son las siguientes:
API muy simple y, por lo tanto, fácil de aprender.
Altamente escalable y flexible.
Proporciona un módulo independiente para probar el componente WebView de Android.
Proporciona un módulo separado para validar y simular Intents de Android.
Proporciona sincronización automática entre su aplicación y pruebas.
Ventajas del espresso
Veamos ahora cuáles son los beneficios de Espresso.
Compatibilidad con versiones anteriores
Fácil de configurar.
Ciclo de prueba muy estable.
También admite actividades de prueba fuera de la aplicación.
Soporta JUnit4
Automatización de interfaz de usuario adecuada para escribir pruebas de caja negra.
En este capítulo, entendamos cómo instalar espresso framework, configurarlo para escribir pruebas de espresso y ejecutarlo en nuestra aplicación de Android.
Prerrequisitos
Espresso es un marco de prueba de interfaz de usuario para probar aplicaciones de Android desarrolladas en lenguaje Java / Kotlin usando SDK de Android. Por lo tanto, el único requisito de espresso es desarrollar la aplicación utilizando el SDK de Android en Java o Kotlin y se recomienda tener la última versión de Android Studio.
La lista de elementos que deben configurarse correctamente antes de comenzar a trabajar en el marco de espresso es la siguiente:
Instale la última versión de Java JDK y configure la variable de entorno JAVA_HOME.
Instale la última versión de Android Studio (versión 3.2. O superior).
Instale el último SDK de Android con SDK Manager y configure la variable de entorno ANDROID_HOME.
Instale la última herramienta de compilación de Gradle y configure la variable de entorno GRADLE_HOME.
Configurar EspressoTesting Framework
Inicialmente, el marco de prueba de espresso se proporciona como parte de la biblioteca de soporte de Android. Más tarde, el equipo de Android proporciona una nueva biblioteca de Android, AndroidX, y traslada el último desarrollo del marco de pruebas de espresso a la biblioteca. El último desarrollo (Android 9.0, API nivel 28 o superior) del marco de prueba de espresso se realizará en la biblioteca de AndroidX.
Incluir el marco de prueba de espresso en un proyecto es tan simple como configurar el marco de prueba de espresso como una dependencia en el archivo gradle de la aplicación, app / build.gradle. La configuración completa es la siguiente,
Usando la biblioteca de soporte de 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 la biblioteca de 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 en android / defaultConfig establece la clase AndroidJUnitRunner para ejecutar las pruebas de instrumentación. La primera línea en las dependencias incluye el marco de prueba JUnit , la segunda línea en las dependencias incluye la biblioteca del corredor de prueba para ejecutar los casos de prueba y finalmente la tercera línea en las dependencias incluye el marco de prueba espresso.
De forma predeterminada, Android Studio establece el marco de prueba espresso (biblioteca de soporte de Android) como una dependencia mientras crea el proyecto de Android y gradle descargará la biblioteca necesaria del repositorio de Maven. Creemos una aplicación simple para Android Hello World y verifiquemos si el marco de prueba de espresso está configurado correctamente.
Los pasos para crear una nueva aplicación de Android se describen a continuación:
Inicie Android Studio.
Seleccione Archivo → Nuevo → Nuevo proyecto.
Ingrese el nombre de la aplicación (HelloWorldApp) y el dominio de la empresa (espressosamples.tutorialspoint.com) y luego haga clic en Siguiente .
Para crear un proyecto de Android,
Seleccione API mínima como API 15: Android 4.0.3 (IceCreamSandwich) y luego haga clic en Siguiente.
Para apuntar a dispositivos Android,
Seleccione Actividad vacía y luego haga clic en Siguiente .
Para agregar una actividad a Mobile,
Ingrese el nombre de la actividad principal y luego haga clic en Finalizar .
Para configurar la actividad,
Una vez que se crea un nuevo proyecto, abra el archivo app / build.gradle y verifique su contenido. El contenido del archivo se especifica a continuación,
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'
}
La última línea especifica la dependencia del marco de prueba de espresso. De forma predeterminada, la biblioteca de soporte de Android está configurada. Podemos reconfigurar la aplicación para usar la biblioteca de AndroidX haciendo clic en Refactor → Migrar a AndroidX en el menú.
Para migrar a Androidx,
Ahora, la aplicación / build.gradle cambia como se especifica a continuación,
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'
}
Ahora, la última línea incluye el marco de prueba espresso de la biblioteca de AndroidX.
Configuración de dispositivo
Durante la prueba, se recomienda desactivar la animación en el dispositivo Android, que se utiliza para la prueba. Esto reducirá las confusiones al comprobar los recursos de diseño.
Veamos cómo deshabilitar la animación en dispositivos Android - (Configuración → Opciones de desarrollador),
Escala de animación de ventana
Escala de animación de transición
Escala de duración del animador
Si el menú de opciones de desarrollador no está disponible en la pantalla Configuración , haga clic en Número de compilación disponible dentro de la opción Acerca del teléfono varias veces. Esto habilita el menú de Opciones de desarrollador .
En este capítulo, veamos cómo ejecutar pruebas con Android Studio.
Cada aplicación de Android tiene dos tipos de pruebas:
Pruebas funcionales / unitarias
Pruebas de instrumentación
La prueba funcional no necesita que la aplicación de Android real se instale e inicie en el dispositivo o emulador y pruebe la funcionalidad. Se puede iniciar en la propia consola sin invocar la aplicación real. Sin embargo, las pruebas de instrumentación necesitan que se inicie la aplicación real para probar la funcionalidad, como la interfaz de usuario y la interacción del usuario. De forma predeterminada, las pruebas unitarias se escriben ensrc/test/java/ La carpeta y las pruebas de instrumentación están escritas en src/androidTest/java/carpeta. Android Studio proporciona el menú contextual Ejecutar para que las clases de prueba ejecuten la prueba escrita en las clases de prueba seleccionadas. De forma predeterminada, una aplicación de Android tiene dos clases: ExampleUnitTest en la carpeta src / test y ExampleInstrumentedTest en la carpeta src / androidTest .
Para ejecutar la prueba unitaria predeterminada, seleccione ExampleUnitTest en el estudio de Android, haga clic derecho sobre él y luego haga clic en Ejecutar 'ExampleUnitTest' como se muestra a continuación,
Ejecutar prueba unitaria
Esto ejecutará la prueba unitaria y mostrará el resultado en la consola como en la siguiente captura de pantalla:
Prueba unitaria exitosa
Para ejecutar la prueba de instrumentación predeterminada, seleccione ExampleInstrumentationTest en el estudio de Android, haga clic con el botón derecho y luego haga clic en Ejecutar 'ExampleInstrumentationTest' como se muestra a continuación,
Ejecutar prueba de instrumentación
Esto ejecutará la prueba unitaria iniciando la aplicación en el dispositivo o emulador y mostrará el resultado en la consola como en la siguiente captura de pantalla:
La prueba de instrumentación se realizó con éxito.
En este capítulo, comprendamos los conceptos básicos de JUnit , el popular marco de pruebas unitarias desarrollado por la comunidad Java sobre el que se construye el marco de pruebas espresso.
JUnit es el estándar de facto para la prueba unitaria de una aplicación Java. A pesar de que es popular para las pruebas unitarias, también tiene soporte completo y provisión para pruebas de instrumentación. La biblioteca de pruebas de Espresso amplía las clases JUnit necesarias para admitir las pruebas de instrumentación basadas en Android.
Escribir una prueba unitaria simple
Creemos una clase Java, Computación (Computation.java) y escribamos una operación matemática simple, Suma y Multiplicación . Luego, escribiremos casos de prueba usando JUnit y lo verificaremos ejecutando los casos de prueba.
Inicie Android Studio.
Abra HelloWorldApp creada en el capítulo anterior.
Cree un archivo, Computation.java en app / src / main / java / com / tutorialspoint / espressosamples / helloworldapp / y escriba dos funciones: Suma y Multiplica como se especifica a continuación,
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;
}
}
Cree un archivo, ComputationUnitTest.java en app / src / test / java / com / tutorialspoint / espressosamples / helloworldapp y escriba casos de prueba unitarios para probar la funcionalidad de suma y multiplicación como se especifica a continuación
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));
}
}
Aquí, hemos utilizado dos términos nuevos: @Test y assertEquals . En general, JUnit usa la anotación de Java para identificar los casos de prueba en una clase e información sobre cómo ejecutar los casos de prueba. @Test es una de esas anotaciones de Java, que especifica que la función en particular es un caso de prueba junit. assertEquals es una función para afirmar que el primer argumento (valor esperado) y el segundo argumento (valor calculado) son iguales. JUnit proporciona varios métodos de afirmación para diferentes escenarios de prueba.
Ahora, ejecute ComputationUnitTest en el estudio de Android haciendo clic con el botón derecho en la clase e invocando la opción Ejecutar 'ComputationUnitTest' como se explica en el capítulo anterior. Esto ejecutará los casos de prueba unitarios e informará el éxito.
El resultado de la prueba unitaria de cálculo se muestra a continuación:
Anotaciones
El marco JUnit utiliza la anotación de forma extensiva . Algunas de las anotaciones importantes son las siguientes:
@Test
@Before
@After
@BeforeClass
@AfterClass
@Rule
@ Anotación de prueba
@Test es la anotación muy importante en el marco JUnit . @Test se utiliza para diferenciar un método normal del método de caso de prueba. Una vez que un método está decorado con la anotación @Test , ese método en particular se considera un caso de prueba y lo ejecutará JUnit Runner . JUnit Runner es una clase especial, que se usa para buscar y ejecutar los casos de prueba JUnit disponibles dentro de las clases java. Por ahora, estamos usando la opción incorporada de Android Studio para ejecutar las pruebas unitarias (que a su vez ejecutan JUnit Runner ). Un código de muestra es el siguiente,
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 de
La anotación @Before se usa para hacer referencia a un método, que debe invocarse antes de ejecutar cualquier método de prueba disponible en una clase de prueba en particular. Por ejemplo, en nuestra muestra, el objeto Computation puede crearse en un método separado y anotarse con @Before para que se ejecute antes del caso de prueba sum_isCorrect y multiply_isCorrect . El código completo es el siguiente,
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));
}
}
@Después
@After es similar a @Before , pero el método anotado con @After se llamará o ejecutará después de ejecutar cada caso de prueba. El código de muestra es el siguiente,
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 de clase
@BeforeClass es similar a @Before , pero el método anotado con @BeforeClass se llamará o ejecutará solo una vez antes de ejecutar todos los casos de prueba en una clase en particular. Es útil crear un objeto de uso intensivo de recursos como un objeto de conexión de base de datos. Esto reducirá el tiempo necesario para ejecutar una colección de casos de prueba. Este método debe ser estático para que funcione correctamente. En nuestra muestra, podemos crear el objeto de cálculo una vez antes de ejecutar todos los casos de prueba como se especifica a continuación,
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));
}
}
@Después de clases
@AfterClass es similar a @BeforeClass , pero el método anotado con @AfterClass se llamará o ejecutará solo una vez después de que se ejecuten todos los casos de prueba en una clase en particular. Este método también debe ser estático para funcionar correctamente. El código de muestra es el siguiente:
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));
}
}
@Regla
La anotación @Rule es uno de los aspectos más destacados de JUnit . Se utiliza para agregar comportamiento a los casos de prueba. Solo podemos anotar los campos de tipo TestRule . En realidad, el conjunto de características proporciona proporcionada por @Before y @After anotación pero de una manera eficiente y reutilizable. Por ejemplo, es posible que necesitemos una carpeta temporal para almacenar algunos datos durante un caso de prueba. Normalmente, necesitamos crear una carpeta temporal antes de ejecutar el caso de prueba (usando la anotación @Before o @BeforeClass) y destruirla después de ejecutar el caso de prueba (usando la anotación @After o @AfterClass). En su lugar, podemos usar la clase TemporaryFolder (de tipo TestRule ) proporcionada por JUnit framework para crear una carpeta temporal para todos nuestros casos de prueba y la carpeta temporal se eliminará cuando se ejecute el caso de prueba. Necesitamos crear una nueva variable de tipo TemporaryFolder y necesitamos anotar con @Rule como se especifica a continuación,
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));
}
}
Orden de ejecución
En JUnit , los métodos anotados con diferentes anotaciones se ejecutarán en un orden específico como se muestra a continuación,
@BeforeClass
@Rule
@Before
@Test
@After
@AfterClass
Afirmación
La afirmación es una forma de comprobar si el valor esperado del caso de prueba coincide con el valor real del resultado del caso de prueba. JUnit proporciona afirmación para diferentes escenarios; Algunas afirmaciones importantes se enumeran a continuación:
fail() - Hacer que un caso de prueba falle explícitamente.
assertTrue(boolean test_condition) - Comprueba que test_condition sea verdadero
assertFalse(boolean test_condition) - Comprueba que test_condition sea falso
assertEquals(expected, actual) - Comprueba que ambos valores sean iguales
assertNull(object) - Comprueba que el objeto sea nulo
assertNotNull(object) - Comprueba que el objeto no sea nulo
assertSame(expected, actual) - Comprueba que ambos se refieren al mismo objeto.
assertNotSame(expected, actual) - Comprueba que ambos se refieren a objeto diferente.
En este capítulo, aprendamos los términos del marco de prueba de espresso, cómo escribir un caso de prueba de espresso simple y el flujo de trabajo completo o la arquitectura del marco de prueba de espresso.
Visión general
Espresso ofrece una gran cantidad de clases para probar la interfaz de usuario y la interacción del usuario de una aplicación de Android. Se pueden agrupar en cinco categorías como se especifica a continuación:
Corredor de JUnit
El marco de prueba de Android proporciona un corredor, AndroidJUnitRunner para ejecutar los casos de prueba espresso escritos en casos de prueba de estilo JUnit3 y JUnit4. Es específico para la aplicación de Android y maneja de manera transparente la carga de los casos de prueba de espresso y la aplicación bajo prueba, tanto en el dispositivo real como en el emulador, ejecuta los casos de prueba e informa el resultado de los casos de prueba. Para usar AndroidJUnitRunner en el caso de prueba, necesitamos anotar la clase de prueba usando la anotación @RunWith y luego pasar el argumento AndroidJUnitRunner como se especifica a continuación:
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
}
Reglas de JUnit
El marco de prueba de Android proporciona una regla, ActivityTestRule, para iniciar una actividad de Android antes de ejecutar los casos de prueba. Lanza la actividad antes de cada método anotado con @ Test` y @Before. Terminará la actividad después del método anotado con @After. Un código de muestra es el siguiente,
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
Aquí, MainActivity es la actividad que se iniciará antes de ejecutar un caso de prueba y se destruirá después de ejecutar el caso de prueba en particular.
ViewMatchers
Espresso proporciona una gran cantidad de clases de comparadores de vistas (en el paquete androidx.test.espresso.matcher.ViewMatchers ) para hacer coincidir y encontrar elementos / vistas de la interfaz de usuario en la jerarquía de vistas de una pantalla de actividad de Android. El método onView de Espresso toma un único argumento de tipo Matcher (View matchers), encuentra la vista de IU correspondiente y devuelve el objeto ViewInteraction correspondiente . El objeto ViewInteraction devuelto por el método onView se puede utilizar para invocar acciones como hacer clic en la vista coincidente o se puede utilizar para afirmar la vista coincidente. Un código de muestra para encontrar la vista con el texto "¡Hola, mundo!" es como sigue,
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
Aquí, withText es un comparador, que se puede usar para hacer coincidir la vista de IU con el texto "¡Hola, mundo!"
ViewActions
Espresso proporciona una gran cantidad de clases de acción de vista (en androidx.test.espresso.action.ViewActions) para invocar la acción diferente en la vista seleccionada / emparejada. Una vez que onView coincide y devuelve el objeto ViewInteraction , se puede invocar cualquier acción llamando al método "perform" del objeto ViewInteraction y pasarlo con las acciones de visualización adecuadas. Un código de muestra para hacer clic en la vista coincidente es el siguiente:
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.perform(click());
Aquí, se invocará la acción de clic de la vista coincidente.
Ver afirmaciones
De manera similar a los comparadores de vistas y las acciones de visualización, Espresso proporciona una gran cantidad de aserciones de vistas (en el paquete androidx.test.espresso.assertion.ViewAssertions ) para afirmar que la vista coincidente es lo que esperábamos. Una vez que onView coincide y devuelve el objeto ViewInteraction , cualquier aserción se puede verificar utilizando el método de verificación de ViewInteraction pasándola con la aserción de vista adecuada. Un código de muestra para afirmar que la vista coincidente es la siguiente,
ViewInteraction viewInteraction = Espresso.onView(withText("Hello World!"));
viewInteraction.check(matches(withId(R.id.text_view)));
Aquí, las coincidencias aceptan el comparador de vistas y devuelven la aserción de vista, que se puede verificar mediante el método de verificación de ViewInteraction .
Flujo de trabajo del marco de prueba de Espresso
Entendamos cómo funciona el marco de prueba de espresso y cómo proporciona opciones para realizar cualquier tipo de interacción con el usuario de una manera simple y flexible. El flujo de trabajo de un caso de prueba de espresso es como se describe a continuación,
Como aprendimos anteriormente, Android JUnit runner, AndroidJUnit4 ejecutará los casos de prueba de Android. Los casos de prueba de espresso deben marcarse con @RunWith (AndroidJUnut.class) . Primero, AndroidJUnit4 preparará el entorno para ejecutar los casos de prueba. Inicia el dispositivo Android conectado o el emulador, instala la aplicación y se asegura de que la aplicación que se va a probar esté lista. Ejecutará los casos de prueba e informará los resultados.
Espresso necesita al menos una única regla JUnit de tipo ActivityTestRule para especificar la actividad. El corredor de Android JUnit iniciará la actividad que se iniciará mediante ActivityTestRule .
Cada caso de prueba necesita un mínimo de invocación del método onView u onDate (utilizado para encontrar vistas basadas en datos como AdapterView ) para que coincida y encuentre la vista deseada. onView u onData devuelve el objeto ViewInteraction .
Una vez que se devuelve el objeto ViewInteraction , podemos invocar una acción de la vista seleccionada o verificar la vista para nuestra vista esperada usando aserción.
La acción se puede invocar utilizando el método perform del objeto ViewInteraction pasando cualquiera de las acciones de vista disponibles.
La aserción se puede invocar mediante el método de verificación del objeto ViewInteraction pasando cualquiera de las aserciones de vista disponibles.
La representación del diagrama del flujo de trabajo es la siguiente,
Ejemplo: ver aserción
Escribamos un caso de prueba simple para encontrar la vista de texto que tiene "¡Hola mundo!" texto en nuestra aplicación "HelloWorldApp" y luego afirmarlo usando la aserción de vista. El código completo es el siguiente,
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());
}
}
Aquí, hemos utilizado con comparadores de vista de texto para encontrar la vista de texto que tiene "¡Hola mundo!" text y coincide con la aserción de vista para afirmar que la vista de texto se muestra correctamente. Una vez que se invoca el caso de prueba en Android Studio, ejecutará el caso de prueba e informará el mensaje de éxito como se muestra a continuación.
view_isCorrect caso de prueba
El marco de Espresso proporciona muchos comparadores de vistas. El propósito del comparador es hacer coincidir una vista usando diferentes atributos de la vista como Id, Texto y disponibilidad de la vista secundaria. Cada comparador coincide con un atributo particular de la vista y se aplica a un tipo particular de vista. Por ejemplo, withId matcher coincide con la propiedad Id de la vista y se aplica a todas las vistas, mientras que withText matcher coincide con la propiedad Text de la vista y se aplica solo a TextView .
En este capítulo, aprendamos los diferentes matchers proporcionados por el marco de prueba de espresso, así como la biblioteca de Hamcrest sobre la que se construyen los matchers de espresso.
Biblioteca Hamcrest
La biblioteca Hamcrest es una biblioteca importante en el ámbito del marco de prueba de espresso. Hamcrest es en sí mismo un marco para escribir objetos de comparación. El marco de Espresso utiliza ampliamente la biblioteca Hamcrest y la amplía siempre que sea necesario para proporcionar comparadores simples y extensibles.
Hamcrest proporciona una función simple assertThat y una colección de comparadores para afirmar cualquier objeto. assertThat tiene tres argumentos y son los que se muestran a continuación:
Cadena (descripción de la prueba, opcional)
Objeto (real)
Matcher (esperado)
Escribamos un ejemplo simple para probar si un objeto de lista tiene un 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"));
}
Aquí, hasItem devuelve un comparador, que comprueba si la lista real tiene un valor especificado como uno de los elementos.
Hamcrest tiene muchos igualadores incorporados y también opciones para crear nuevos igualadores. Algunos de los matchers incorporados importantes útiles en el marco de prueba de espresso son los siguientes:
cualquier cosa - siempre coincidentes
Comparadores basados en lógica
allOf - Acepte cualquier número de igualadores y coincidencias solo si todos los igualadores tienen éxito.
anyOf - aceptar cualquier número de comparadores y coincidencias si alguno de ellos tuvo éxito.
not - aceptar un emparejador y coincide solo si el emparejador falló y viceversa.
Comparadores basados en texto
equalToIgnoringCase - utilizado para probar si la entrada real es igual a la cadena esperada ignorando el caso.
equalToIgnoringWhiteSpace - utilizado para probar si la entrada real es igual a la cadena especificada ignorando el caso y los espacios en blanco.
containsString : Se utiliza para probar si la entrada real contiene una cadena especificada.
endsWith : Se utiliza para probar si la entrada real comienza con una cadena especificada.
startsWith - utilizado para probar si la entrada finaliza con una cadena especificada.
Comparadores basados en números
closeTo - utilizado para probar si la entrada real está cerca del número esperado.
greaterThan - utilizado para probar si la entrada real es mayor que el número esperado.
greaterThanOrEqualTo - se utiliza para probar si la entrada real es mayor o igual que el número esperado.
lessThan - utilizado para probar si la entrada real es menor que el número esperado.
lessThanOrEqualTo - se utiliza para probar si la entrada real es menor o igual que el número esperado.
Comparadores basados en objetos
equalTo - utilizado para probar si la entrada real es igual al objeto esperado
hasToString - utilizado para probar si la entrada real tiene el método toString.
instanceOf - utilizado para probar si la entrada real es la instancia de la clase esperada.
isCompatibleType - se utiliza para probar si la entrada real es compatible con el tipo esperado.
notNullValue - utilizado para probar si la entrada real no es nula.
sameInstance - utilizado para probar si la entrada real y la esperada son de la misma instancia.
hasProperty - utilizado para probar si la entrada real tiene la propiedad esperada
es - Azúcar o atajo para equalTo
Matchers
Espresso proporciona el método onView () para hacer coincidir y encontrar las vistas. Acepta comparadores de vistas y devuelve el objeto ViewInteraction para interactuar con la vista coincidente. La lista de comparadores de vistas de uso frecuente se describe a continuación:
withId ()
withId () acepta un argumento de tipo int y el argumento se refiere al id de la vista. Devuelve un comparador, que coincide con la vista utilizando el id de la vista. El código de muestra es el siguiente,
onView(withId(R.id.testView))
withText ()
withText () acepta un argumento de tipo cadena y el argumento se refiere al valor de la propiedad de texto de la vista. Devuelve un comparador, que coincide con la vista utilizando el valor de texto de la vista. Se aplica solo a TextView . El código de muestra es el siguiente,
onView(withText("Hello World!"))
withContentDescription ()
withContentDescription () acepta un argumento de tipo cadena y el argumento hace referencia al valor de la propiedad de descripción del contenido de la vista. Devuelve un comparador, que coincide con la vista utilizando la descripción de la vista. El código de muestra es el siguiente,
onView(withContentDescription("blah"))
También podemos pasar la identificación del recurso del valor del texto en lugar del texto en sí.
onView(withContentDescription(R.id.res_id_blah))
hasContentDescription ()
hasContentDescription () no tiene argumento. Devuelve un comparador, que coincide con la vista que tiene cualquier descripción de contenido. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), hasContentDescription()))
withTagKey ()
withTagKey () acepta un argumento de tipo cadena y el argumento se refiere a la clave de etiqueta de la vista. Devuelve un comparador, que coincide con la vista usando su clave de etiqueta. El código de muestra es el siguiente,
onView(withTagKey("blah"))
También podemos pasar el ID de recurso del nombre de la etiqueta en lugar del nombre de la etiqueta en sí.
onView(withTagKey(R.id.res_id_blah))
withTagValue ()
withTagValue () acepta un argumento de tipo Matcher <Object> y el argumento se refiere al valor de la etiqueta de la vista. Devuelve un comparador, que coincide con la vista utilizando su valor de etiqueta. El código de muestra es el siguiente,
onView(withTagValue(is((Object) "blah")))
En este caso, es que es Hamcrest de coincidencias.
withClassName ()
withClassName () acepta un argumento de tipo Matcher <String> y el argumento se refiere al valor del nombre de clase de la vista. Devuelve un comparador, que coincide con la vista usando su nombre de clase. El código de muestra es el siguiente,
onView(withClassName(endsWith("EditText")))
Aquí, termina con Hamcrest Matcher y devuelve Matcher <String>
withHint ()
withHint () acepta un argumento de tipo Matcher <String> y el argumento hace referencia al valor de sugerencia de la vista. Devuelve un comparador, que coincide con la vista utilizando la sugerencia de la vista. El código de muestra es el siguiente,
onView(withClassName(endsWith("Enter name")))
withInputType ()
withInputType () acepta un argumento de tipo int y el argumento se refiere al tipo de entrada de la vista. Devuelve un comparador, que coincide con la vista utilizando su tipo de entrada. El código de muestra es el siguiente,
onView(withInputType(TYPE_CLASS_DATETIME))
Aquí, TYPE_CLASS_DATETIME hace referencia a la vista de edición que admite fechas y horas.
withResourceName ()
withResourceName () acepta un argumento de tipo Matcher <String> y el argumento hace referencia al valor del nombre de clase de la vista. Devuelve un comparador, que coincide con la vista utilizando el nombre del recurso de la vista. El código de muestra es el siguiente,
onView(withResourceName(endsWith("res_name")))
También acepta argumentos de cadena. El código de muestra es el siguiente,
onView(withResourceName("my_res_name"))
withAlpha ()
withAlpha () acepta un argumento de tipo float y el argumento se refiere al valor alfa de la vista. Devuelve un comparador, que coincide con la vista utilizando el valor alfa de la vista. El código de muestra es el siguiente,
onView(withAlpha(0.8))
withEffectiveVisibility ()
withEffectiveVisibility () acepta un argumento de tipo ViewMatchers.Visibility y el argumento se refiere a la visibilidad efectiva de la vista. Devuelve un comparador, que coincide con la vista utilizando la visibilidad de la vista. El código de muestra es el siguiente,
onView(withEffectiveVisibility(withEffectiveVisibility.INVISIBLE))
withSpinnerText ()
withSpinnerText () acepta un argumento de tipo Matcher <String> y el argumento se refiere al valor actual de la vista seleccionada del Spinner. Devuelve un matcher, que coincide con el spinner en función del valor toString del elemento seleccionado. El código de muestra es el siguiente,
onView(withSpinnerText(endsWith("USA")))
También acepta el argumento de cadena o la identificación de recurso de la cadena. El código de muestra es el siguiente,
onView(withResourceName("USA"))
onView(withResourceName(R.string.res_usa))
withSubstring ()
withSubString () es similar a withText () excepto que ayuda a probar la subcadena del valor de texto de la vista.
onView(withSubString("Hello"))
hasLinks ()
hasLinks () no tiene argumentos y devuelve un comparador, que coincide con la vista que tiene enlaces. Se aplica solo a TextView. El código de muestra es el siguiente,
onView(allOf(withSubString("Hello"), hasLinks()))
Aquí, allOf es un comparador de Hamcrest. allOf devuelve un comparador, que coincide con todos los comparadores pasados y, en este caso, se usa para hacer coincidir una vista y comprobar si la vista tiene enlaces en su valor de texto.
hasTextColor ()
hasTextColor () acepta un solo argumento de tipo int y el argumento se refiere al ID de recurso del color. Devuelve un matcher, que coincide con TextView según su color. Se aplica solo a TextView . El código de muestra es el siguiente,
onView(allOf(withSubString("Hello"), hasTextColor(R.color.Red)))
hasEllipsizedText ()
hasEllipsizedText () no tiene argumento. Devuelve un comparador, que coincide con el TextView que tiene texto largo y elipsizado (primero ... diez ... último) o cortado (primero ...). El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_text_view_id), hasEllipsizedText()))
hasMultilineText ()
hasMultilineText () no tiene argumento. Devuelve un comparador, que coincide con el TextView que tiene texto de varias líneas. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_test_view_id), hasMultilineText()))
hasBackground ()
hasBackground () acepta un solo argumento de tipo int y el argumento hace referencia al ID de recurso del recurso de fondo. Devuelve un comparador, que coincide con la vista en función de sus recursos de fondo. El código de muestra es el siguiente,
onView(allOf(withId("image"), hasBackground(R.drawable.your_drawable)))
hasErrorText ()
hasErrorText () acepta un argumento de tipo Matcher <String> y el argumento hace referencia al valor de la cadena de error de la vista (EditText). Devuelve un comparador, que coincide con la vista utilizando la cadena de error de la vista. Esto se aplica solo a EditText . El código de muestra es el siguiente,
onView(allOf(withId(R.id.editText_name), hasErrorText(is("name is required"))))
También acepta argumentos de cadena. El código de muestra es el siguiente,
onView(allOf(withId(R.id.editText_name), hasErrorText("name is required")))
hasImeAction ()
hasImeAction () acepta un argumento de tipo Matcher <Integer> y el argumento hace referencia a los métodos de entrada compatibles con la vista (EditText). Devuelve un comparador, que coincide con la vista utilizando el método de entrada admitido de la vista. Esto se aplica solo a EditText . El código de muestra es el siguiente,
onView(allOf(withId(R.id.editText_name),
hasImeAction(is(EditorInfo.IME_ACTION_GO))))
Aquí, EditorInfo.IME_ACTION_GO es una de las opciones de métodos de entrada. hasImeAction () también acepta argumentos enteros. El código de muestra es el siguiente,
onView(allOf(withId(R.id.editText_name),
hasImeAction(EditorInfo.IME_ACTION_GO)))
supportsInputMethods ()
supportsInputMethods () no tiene argumento. Devuelve un comparador, que coincide con la vista si admite métodos de entrada. El código de muestra es el siguiente,
onView(allOf(withId(R.id.editText_name), supportsInputMethods()))
isRoot ()
isRoot () no tiene argumento. Devuelve un comparador, que coincide con la vista raíz. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_root_id), isRoot()))
se visualiza()
isDisplayed () no tiene argumento. Devuelve un comparador, que coincide con la vista que se muestra actualmente. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isDisplayed()))
isDisplayingAtLeast ()
isDisplayingAtLeast () acepta un solo argumento de tipo int. Devuelve un comparador, que coincide con la vista que se muestra actualmente al menos el porcentaje especificado. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isDisplayingAtLeast(75)))
isCompletelyDisplayed ()
isCompletelyDisplayed () no tiene argumento. Devuelve un comparador, que coincide con la vista que se muestra actualmente completamente en la pantalla. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isCompletelyDisplayed()))
está habilitado()
isEnabled () no tiene argumento. Devuelve un comparador, que coincide con la vista que está habilitada. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isEnabled()))
isFocusable ()
isFocusable () no tiene argumento. Devuelve un comparador, que coincide con la vista que tiene la opción de enfoque. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isFocusable()))
hasFocus ()
hasFocus () no tiene argumento. Devuelve un comparador, que coincide con la vista que está enfocada actualmente. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), hasFocus()))
isClickable ()
isClickable () no tiene argumento. Devuelve un comparador, que coincide con la vista que es la opción de clic. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isClickable()))
isSelected ()
isSelected () no tiene argumento. Devuelve un comparador, que coincide con la vista que está seleccionada actualmente. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isSelected()))
está chequeado()
isChecked () no tiene argumento. Devuelve un comparador, que coincide con la vista que es de tipo CompoundButton (o subtipo) y está en estado marcado. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_view_id), isChecked()))
isNotChecked ()
isNotChecked () es justo lo opuesto a isChecked. El código de muestra es el siguiente *,
onView(allOf(withId(R.id.my_view_id), isNotChecked()))
isJavascriptEnabled ()
isJavascriptEnabled () no tiene argumento. Devuelve un comparador, que coincide con WebView que está evaluando JavaScript. El código de muestra es el siguiente,
onView(allOf(withId(R.id.my_webview_id), isJavascriptEnabled()))
con los padres()
withParent () acepta un argumento de tipo Matcher <View>. El argumento remite a una vista. Devuelve un comparador, que coincide con la vista que la vista especificada es la vista principal. El código de muestra es el siguiente,
onView(allOf(withId(R.id.childView), withParent(withId(R.id.parentView))))
hasSibling ()
hasSibling () acepta un argumento de tipo Matcher> View <. El argumento remite a una vista. Devuelve un comparador, que coincide con la vista que la vista pasada es una de sus vistas hermanas. El código de muestra es el siguiente,
onView(hasSibling(withId(R.id.siblingView)))
con niño()
withChild () acepta un argumento de tipo Matcher <View>. El argumento remite a una vista. Devuelve un comparador, que coincide con la vista que la vista pasada es vista secundaria. El código de muestra es el siguiente,
onView(allOf(withId(R.id.parentView), withChild(withId(R.id.childView))))
hasChildCount ()
hasChildCount () acepta un argumento de tipo int. El argumento se refiere al recuento secundario de una vista. Devuelve un comparador, que coincide con la vista que tiene exactamente el mismo número de vistas secundarias que se especifica en el argumento. El código de muestra es el siguiente,
onView(hasChildCount(4))
hasMinimumChildCount ()
hasMinimumChildCount () acepta un argumento de tipo int. El argumento se refiere al recuento secundario de una vista. Devuelve un comparador, que coincide con la vista que tiene al menos el número de vistas secundarias como se especifica en el argumento. El código de muestra es el siguiente,
onView(hasMinimumChildCount(4))
hasDescendant ()
hasDescendant () acepta un argumento de tipo Matcher <View>. El argumento remite a una vista. Devuelve un comparador, que coincide con la vista que la vista pasada es una de las vistas descendientes en la jerarquía de vistas. El código de muestra es el siguiente,
onView(hasDescendant(withId(R.id.descendantView)))
isDescendantOfA ()
isDescendantOfA () acepta un argumento de tipo Matcher <View>. El argumento remite a una vista. Devuelve un comparador, que coincide con la vista que la vista pasada es una de las vistas anteriores en la jerarquía de vistas. El código de muestra es el siguiente,
onView(allOf(withId(R.id.myView), isDescendantOfA(withId(R.id.parentView))))
Espresso ofrece varias opciones para crear nuestros propios comparadores de vistas personalizados y se basa en los comparadores Hamcrest. Custom matcher es un concepto muy potente para ampliar el framework y también para personalizar el framework a nuestro gusto. Algunas de las ventajas de escribir comparadores personalizados son las siguientes:
Para aprovechar la característica única de nuestras propias vistas personalizadas
El comparador personalizado ayuda en los casos de prueba basados en AdapterView a coincidir con los diferentes tipos de datos subyacentes.
Para simplificar los comparadores actuales mediante la combinación de características de varios comparadores
Podemos crear un nuevo comparador cuando surja la demanda y es bastante fácil. Creemos un nuevo comparador personalizado, que devuelve un comparador para probar tanto la identificación como el texto de un TextView .
Espresso ofrece las siguientes dos clases para escribir nuevos comparadores:
TypeSafeMatcher
BoundedMatcher
Ambas clases son de naturaleza similar excepto que BoundedMatcher maneja de manera transparente la conversión del objeto al tipo correcto sin verificar manualmente el tipo correcto. Crearemos un nuevo comparador, con IdAndText usando la clase BoundedMatcher . Revisemos los pasos para escribir nuevos comparadores.
Agregue la siguiente dependencia en el archivo app / build.gradle y sincronícela.
dependencies {
implementation 'androidx.test.espresso:espresso-core:3.1.1'
}
Cree una nueva clase para incluir nuestros comparadores (métodos) y márquela como final
public final class MyMatchers {
}
Declare un método estático dentro de la nueva clase con los argumentos necesarios y establezca Matcher <View> como tipo de retorno.
public final class MyMatchers {
@NonNull
public static Matcher<View> withIdAndText(final Matcher<Integer>
integerMatcher, final Matcher<String> stringMatcher) {
}
}
Cree un nuevo objeto BoundedMatcher (valor de retorno también) con la siguiente firma dentro del 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) {
};
}
}
Invalidar describeTo y matchesSafely métodos en el BoundedMatcher objeto. describeTo tiene un solo argumento de tipo Descripción sin tipo de retorno y se usa para obtener información de error con respecto a los comparadores. MatchSafely tiene un solo argumento de tipo TextView con tipo de retorno booleano y se usa para hacer coincidir la vista.
La versión final del código es la siguiente,
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());
}
};
}
}
Finalmente, podemos usar nuestro comparador de mew para escribir el caso de prueba como se muestra a continuación,
@Test
public void view_customMatcher_isCorrect() {
onView(withIdAndText(is((Integer) R.id.textView_hello), is((String) "Hello World!")))
.check(matches(withText("Hello World!")));
}
Como se discutió anteriormente, la aserción de vista se usa para afirmar que tanto la vista real (que se encuentra usando los comparadores de vista) como las vistas esperadas son iguales. Un código de muestra es el siguiente,
onView(withId(R.id.my_view)) .check(matches(withText("Hello")))
Aquí,
onView () devuelve el objeto ViewInteration correspondiente a la vista coincidente. ViewInteraction se utiliza para interactuar con la vista emparejada.
withId (R.id.my_view) devuelve un comparador de vistas que coincidirá con la vista (real) que tiene atributos de identificación iguales a my_view .
withText ("Hola") también devuelve un comparador de vistas que coincidirá con la vista (esperada) que tiene atributos de texto iguales a Hola .
check es un método que acepta un argumento de tipo ViewAssertion y realiza una aserción utilizando el objeto ViewAssertion .
Match (withText ("Hello")) devuelve una aserción de vista, que hará elreal jobde afirmar que tanto la vista real (encontrada usando withId ) como la vista esperada (encontrada usando withText ) son una y la misma.
Aprendamos algunos de los métodos proporcionados por el marco de pruebas espresso para afirmar los objetos de vista.
no existe()
Devuelve una aserción de vista, lo que garantiza que el comparador de vistas no encuentre ninguna vista coincidente.
onView(withText("Hello")) .check(doesNotExist());
Aquí, el caso de prueba garantiza que no haya ninguna vista con el texto Hola.
partidos()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y coincide con la vista que coincide con el comparador de vistas de destino.
onView(withId(R.id.textView_hello)) .check(matches(withText("Hello World!")));
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.textView_hello existe y coincide con la vista de destino con el texto Hello World!
isBottomAlignedWith ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está alineado en la parte inferior con el comparador de vistas de destino.
onView(withId(R.id.view)) .check(isBottomAlignedWith(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está alineada en la parte inferior con la vista que tiene id, R.id.target_view .
isCompletelyAbove ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está colocado completamente por encima del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isCompletelyAbove(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y se coloca completamente por encima de la vista que tiene id, R.id.target_view
isCompletelyBelow ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está ubicado completamente debajo del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isCompletelyBelow(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está ubicada completamente debajo de la vista que tiene id, R.id.target_view .
isCompletelyLeftOf ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está ubicado completamente a la izquierda del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isCompletelyLeftOf(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y se coloca completamente a la izquierda de la vista con id, R.id.target_view
isCompletelyRightOf ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) exista y esté ubicado completamente a la derecha del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isCompletelyRightOf(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está ubicada completamente a la derecha de la vista que tiene id, R.id.target_view.
isLeftAlignedWith ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y se deja alineado con el comparador de vistas de destino.
onView(withId(R.id.view)) .check(isLeftAlignedWith(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y se deja alineada con la vista que tiene id, R.id.target_view
isPartiallyAbove ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y se coloca parcialmente encima del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isPartiallyAbove(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está colocada parcialmente sobre la vista que tiene id, R.id.target_view
isPartiallyBelow ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y se coloca parcialmente debajo del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isPartiallyBelow(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y se coloca parcialmente debajo de la vista que tiene id, R.id.target_view .
isPartiallyLeftOf ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está ubicado parcialmente a la izquierda del comparador de vistas de destino.
onView(withId(R.id.view)) .check(isPartiallyLeftOf(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y se coloca parcialmente a la izquierda de la vista con id, R.id.target_view .
isPartiallyRightOf ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está ubicado parcialmente a la derecha del comparador de vistas de destino
onView(withId(R.id.view)) .check(isPartiallyRightOf(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está colocada parcialmente a la derecha de la vista con id, R.id.target_view .
isRightAlignedWith ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está alineado a la derecha con el comparador de vistas de destino.
onView(withId(R.id.view)) .check(isRightAlignedWith(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está alineada a la derecha con la vista que tiene id, R.id.target_view .
isTopAlignedWith ()
Acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que el comparador de vistas (real) existe y está alineado en la parte superior con el comparador de vistas de destino.
onView(withId(R.id.view)) .check(isTopAlignedWith(withId(R.id.target_view)))
Aquí, el caso de prueba asegura que la vista que tiene id, R.id.view existe y está alineada en la parte superior con la vista que tiene id, R.id.target_view
noEllipsizedText ()
Devuelve una aserción de vista, lo que garantiza que la jerarquía de vista no contenga vistas de texto elipsizado o cortado.
onView(withId(R.id.view)) .check(noEllipsizedText());
noMultilineButtons ()
Devuelve una aserción de vista, lo que garantiza que la jerarquía de vista no contenga botones de varias líneas.
onView(withId(R.id.view)) .check(noMultilineButtons());
noOverlaps ()
Devuelve una aserción de vista, que garantiza que el objeto descendiente asignable a TextView o ImageView no se superponga entre sí. Tiene otra opción, que acepta un comparador de vistas de destino y devuelve una aserción de vista, lo que garantiza que la vista descendiente que coincide con la vista de destino no se superponga.
Como se aprendió anteriormente, las acciones de visualización automatizan todas las acciones posibles que pueden realizar los usuarios en una aplicación de Android. Espresso onView y "onData" proporcionan el método perform , que acepta acciones de visualización e invoca / automatiza las acciones de usuario correspondientes en el entorno de prueba. Por ejemplo, "click ()" es una acción de visualización, que cuando se pasa al método onView ( R.id.myButton ) .perform (click ()) , activará el evento click del botón (con id: "myButton" ) en el entorno de prueba.
En este capítulo, aprendamos sobre las acciones de visualización proporcionadas por el marco de prueba de espresso.
teclee el texto()
typeText () acepta un argumento (texto) de tipo String y devuelve una acción de visualización. La acción de vista devuelta escribe el texto proporcionado en la vista. Antes de colocar el texto, toca la vista una vez. El contenido puede colocarse en una posición arbitraria si ya contiene texto.
onView(withId(R.id.text_view)).perform(typeText("Hello World!"))
typeTextIntoFocusedView ()
typeTextIntoFocusedView () es similar a typeText () excepto que coloca el texto justo al lado de la posición del cursor en la vista.
onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))
replaceText ()
replaceText () es similar a typeText () excepto que reemplaza el contenido de la vista.
onView(withId(R.id.text_view)).perform(typeTextIntoFocusedView("Hello World!"))
Borrar texto()
clearText () no tiene argumentos y devuelve una acción de vista, que borrará el texto en la vista.
onView(withId(R.id.text_view)).perform(clearText())
pressKey ()
pressKey () acepta el código de la tecla (por ejemplo, KeyEvent.KEYCODE_ENTER) y devuelve una acción de visualización, que presionará la tecla corresponde al código de la tecla.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", pressKey(KeyEvent.KEYCODE_ENTER))
pressMenuKey ()
pressMenuKey () no tiene argumentos y devuelve una acción de visualización, que presionará la tecla del menú del hardware.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", pressKey(KeyEvent.KEYCODE_ENTER), pressMenuKey())
closeSoftKeyboard ()
closeSoftKeyboard () no tiene argumentos y devuelve una acción de vista, que cerrará el teclado, si se abre uno.
onView(withId(R.id.text_view)).perform(typeText(
"Hello World!", closeSoftKeyboard())
hacer clic()
click () no tiene argumentos y devuelve una acción de vista, que invocará la acción de clic de la vista.
onView(withId(R.id.button)).perform(click())
haga doble clic()
doubleClick () no tiene argumentos y devuelve una acción de vista, que invocará la acción de doble clic de la vista.
onView(withId(R.id.button)).perform(doubleClick())
longClick ()
longClick () no tiene argumentos y devuelve una acción de vista, que invocará la acción de clic largo de la vista.
onView(withId(R.id.button)).perform(longClick())
pressBack ()
pressBack () no tiene argumentos y devuelve una acción de visualización, que hará clic en el botón Atrás.
onView(withId(R.id.button)).perform(pressBack())
pressBackUnconditionally ()
pressBackUnconditionally () no tiene argumentos y devuelve una acción de vista, que hará clic en el botón Atrás y no arrojará una excepción si la acción del botón Atrás sale de la aplicación.
onView(withId(R.id.button)).perform(pressBack())
enlace abierto()
openLink () tiene dos argumentos. El primer argumento (texto del enlace) es de tipo Matcher y hace referencia al texto de la etiqueta de anclaje HTML. El segundo argumento (url) es del tipo Matcher y hace referencia a la url de la etiqueta de anclaje HTML. Es aplicable solo para TextView . Devuelve una acción de visualización, que recopila todas las etiquetas de anclaje HTML disponibles en el contenido de la vista de texto, encuentra la etiqueta de anclaje que coincide con el primer argumento (texto del enlace) y el segundo argumento (url) y finalmente abre la URL correspondiente. Consideremos una vista de texto que tiene el contenido como:
<a href="http://www.google.com/">copyright</a>
Luego, el enlace se puede abrir y probar utilizando el siguiente caso de prueba,
onView(withId(R.id.text_view)).perform(openLink(is("copyright"),
is(Uri.parse("http://www.google.com/"))))
Aquí, openLink obtendrá el contenido de la vista de texto, encontrará el enlace con derechos de autor como texto, www.google.com como URL y abrirá la URL en un navegador.
openLinkWithText ()
openLinkWithText () tiene un argumento, que puede ser de tipo ** String * o Matcher. Es simplemente un atajo al método openLink *.
onView(withId(R.id.text_view)).perform(openLinkWithText("copyright"))
openLinkWithUri ()
openLinkWithUri () tiene un argumento, que puede ser de tipo String o Matcher. Es simplemente un breve corte en el Openlink método *.
onView(withId(R.id.text_view)).perform(openLinkWithUri("http://www.google.com/"))
pressImeActionButton ()
pressImeActionButton () no tiene argumentos y devuelve una acción de vista, que ejecutará la acción establecida en la configuración de android: imeOptions . Por ejemplo, si android: imeOptions es igual a actionNext, esto moverá el cursor a la siguiente vista de EditText posible en la pantalla.
onView(withId(R.id.text_view)).perform(pressImeActionButton())
scrollTo ()
scrollTo () no tiene argumentos y devuelve una acción de vista, que desplazará el scrollView coincidente en la pantalla.
onView(withId(R.id.scrollView)).perform(scrollTo())
deslizar hacia abajo()
swipeDown () no tiene argumentos y devuelve una acción de visualización, que activará la acción de deslizar hacia abajo en la pantalla.
onView(withId(R.id.root)).perform(swipeDown())
muévase hacia arriba()
swipeUp () no tiene argumentos y devuelve una acción de visualización, que activará la acción de deslizar hacia arriba en la pantalla.
onView(withId(R.id.root)).perform(swipeUp())
desliza a la derecha()
swipeRight () no tiene argumentos y devuelve una acción de visualización, que activará la acción de deslizar hacia la derecha en la pantalla.
onView(withId(R.id.root)).perform(swipeRight())
deslizar a la izquierda()
swipeLeft () no tiene argumentos y devuelve una acción de visualización, que activará la acción de deslizar hacia la izquierda en la pantalla.
onView(withId(R.id.root)).perform(swipeLeft())
AdapterView es un tipo especial de vista diseñado específicamente para representar una colección de información similar, como la lista de productos y los contactos de los usuarios, obtenidos de una fuente de datos subyacente mediante Adapter . La fuente de datos puede ser una lista simple de entradas de bases de datos complejas. Algunas de las vistas derivadas de AdapterView son ListView , GridView y Spinner .
AdapterView representa la interfaz de usuario de forma dinámica según la cantidad de datos disponibles en la fuente de datos subyacente. Además, AdapterView procesa solo los datos mínimos necesarios, que se pueden procesar en el área visible disponible de la pantalla. AdapterView hace esto para conservar memoria y para que la interfaz de usuario se vea fluida incluso si los datos subyacentes son grandes.
Tras el análisis, la naturaleza de la arquitectura AdapterView hace que la opción onView y sus comparadores de vistas sean irrelevantes porque es posible que la vista particular que se va a probar no se muestre en absoluto en primer lugar. Afortunadamente, espresso proporciona un método, onData ( ), que acepta comparadores hamcrest (relevantes para el tipo de datos de los datos subyacentes) para hacer coincidir los datos subyacentes y devuelve el objeto de tipo DataInteraction correspondiente a la vista de los datos coincidentes. Un código de muestra es el siguiente,
onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())
Aquí, onData () coincide con la entrada "Apple", si está disponible en los datos subyacentes (lista de matriz) y devuelve el objeto DataInteraction para interactuar con la vista coincidente (TextView correspondiente a la entrada "Apple").
Métodos
DataInteraction proporciona los siguientes métodos para interactuar con la vista,
realizar()
Esto acepta acciones de visualización y activa las acciones de visualización pasadas.
onData(allOf(is(instanceOf(String.class)), startsWith("Apple"))).perform(click())
cheque()
Esto acepta aserciones de vista y verifica las aserciones de vista pasadas.
onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
.check(matches(withText("Apple")))
inAdapterView ()
Esto acepta ver comparadores. Selecciona el AdapterView particular en función de los comparadores de vista pasados y devuelve el objeto DataInteraction para interactuar con el AdapterView coincidente
onData(allOf())
.inAdapterView(withId(R.id.adapter_view))
.atPosition(5)
.perform(click())
atPosition ()
Acepta un argumento de tipo entero y hace referencia a la posición del elemento en los datos subyacentes. Selecciona la vista correspondiente al valor posicional pasado de los datos y devuelve el objeto DataInteraction para interactuar con la vista coincidente. Será útil si conocemos el orden correcto de los datos subyacentes.
onData(allOf())
.inAdapterView(withId(R.id.adapter_view))
.atPosition(5)
.perform(click())
onChildView ()
Esto acepta comparadores de vista y coincide con la vista dentro de la vista secundaria específica. Por ejemplo, podemos interactuar con elementos específicos como el botón Comprar en un AdapterView basado en una lista de productos .
onData(allOf(is(instanceOf(String.class)), startsWith("Apple")))
.onChildView(withId(R.id.buy_button))
.perform(click())
Escribir una aplicación de muestra
Siga los pasos que se muestran a continuación para escribir una aplicación simple basada en AdapterView y escriba un caso de prueba usando el método onData () .
Inicie Android Studio.
Cree un nuevo proyecto como se discutió anteriormente y asígnele el nombre MyFruitApp .
Migre la aplicación al marco de AndroidX usando Refactor → Migrar al menú de opciones de AndroidX .
Elimine el diseño predeterminado en la actividad principal y agregue ListView . El contenido de activity_main.xml es el siguiente,
<?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>
Agregue un nuevo recurso de diseño, item.xml para especificar la plantilla de elementos de la vista de lista. El contenido del item.xml es el siguiente,
<?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"
/>
Ahora, cree un adaptador que tenga una matriz de frutas como datos subyacentes y configúrelo en la vista de lista. Esto debe hacerse en el onCreate () de MainActivity como se especifica a continuación,
@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);
}
Ahora, compile el código y ejecute la aplicación. La captura de pantalla de la aplicación My Fruit es la siguiente:
Ahora, abra el archivo ExampleInstrumentedTest.java y agregue ActivityTestRule como se especifica a continuación,
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
Además, asegúrese de que la configuración de prueba se realice en 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'
}
Agregue un nuevo caso de prueba para probar la vista de lista como se muestra a continuación,
@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());
}
Finalmente, ejecute el caso de prueba usando el menú contextual de Android Studio y verifique si todos los casos de prueba están teniendo éxito.
WebView es una vista especial proporcionada por Android para mostrar páginas web dentro de la aplicación. WebView no proporciona todas las funciones de una aplicación de navegador completa como Chrome y Firefox. Sin embargo, proporciona un control completo sobre el contenido que se mostrará y expone todas las funciones de Android para que se invoquen dentro de las páginas web. Permite WebView y proporciona un entorno especial donde la interfaz de usuario se puede diseñar fácilmente utilizando tecnología HTML y características nativas como la cámara y marcar un contacto. Este conjunto de características permite que WebView proporcione un nuevo tipo de aplicación llamada aplicación híbrida , donde la interfaz de usuario se realiza en HTML y la lógica empresarial se realiza en JavaScript o mediante un punto final de API externo.
Normalmente, probar un WebView debe ser un desafío porque utiliza tecnología HTML para sus elementos de interfaz de usuario en lugar de la interfaz de usuario / vistas nativas. Espresso sobresale en esta área al proporcionar un nuevo conjunto de comparadores web y afirmaciones web, que es intencionalmente similar a los comparadores de vistas nativas y afirmaciones de vistas. Al mismo tiempo, proporciona un enfoque bien equilibrado al incluir también un entorno de prueba basado en tecnología web.
Espresso web se basa en el marco WebDriver Atom, que se utiliza para buscar y manipular elementos web. Atom es similar a ver acciones. Atom hará toda la interacción dentro de una página web. WebDriver expone un conjunto predefinido de métodos, como findElement () , getElement () para encontrar elementos web y devuelve los átomos correspondientes (para realizar una acción en la página web).
Una declaración de prueba web estándar se parece al código siguiente,
onWebView()
.withElement(Atom)
.perform(Atom)
.check(WebAssertion)
Aquí,
onWebView () : similar a onView (), expone un conjunto de API para probar un WebView.
withElement () : uno de los varios métodos utilizados para ubicar elementos web dentro de una página web usando Atom y devuelve el objeto WebInteration, que es similar a ViewInteraction.
perform () : ejecuta la acción dentro de una página web usando Atom y devuelve WebInteraction.
check () : esto hace la aserción necesaria usando WebAssertion.
Un código de prueba web de muestra es el siguiente:
onWebView()
.withElement(findElement(Locator.ID, "apple"))
.check(webMatches(getText(), containsString("Apple")))
Aquí,
findElement () localiza un elemento y devuelve un átomo
webMatches es similar al método de coincidencias
Escribir una aplicación de muestra
Escribamos una aplicación simple basada en WebView y escribamos un caso de prueba usando el método onWebView () . Siga estos pasos para escribir una aplicación de muestra:
Inicie Android Studio.
Cree un nuevo proyecto como se discutió anteriormente y asígnele el nombre MyWebViewApp .
Migre la aplicación al marco de AndroidX usando Refactor → Migrar al menú de opciones de AndroidX .
Agregue la siguiente opción de configuración en el archivo AndroidManifest.xml para otorgar permiso para acceder a Internet.
<uses-permission android:name = "android.permission.INTERNET" />
Espresso web se proporciona como un complemento independiente. Entonces, agregue la dependencia en la aplicación / build.gradle y sincronícela.
dependencies {
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.1'
}
Elimine el diseño predeterminado en la actividad principal y agregue WebView. El contenido de activity_main.xml es el siguiente,
<?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>
Cree una nueva clase, ExtendedWebViewClient que amplíe WebViewClient y anule el método shouldOverrideUrlLoading para cargar la acción de enlace en el mismo WebView ; de lo contrario, abrirá una nueva ventana del navegador fuera de la aplicación. Colóquelo en MainActivity.java .
private class ExtendedWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
Ahora, agregue el siguiente código en el método onCreate de MainActivity . El propósito del código es encontrar WebView , configurarlo correctamente y finalmente cargar la 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");
Aquí,
El contenido de index.html es el siguiente:
<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>
El contenido del archivo apple.html al que se hace referencia en index.html es el siguiente:
<html>
<head>
<title>Android Web View Sample</title>
</head>
<body>
<h1>Apple</h1>
</body>
</html>
El contenido del archivo banana.html al que se hace referencia en banana.html es el siguiente:
<html>
<head>
<title>Android Web View Sample</title>
</head>
<body>
<h1>Banana</h1>
</body>
</html>
Coloque index.html, apple.html y banana.html en un servidor web
Reemplace la URL en el método loadUrl con su URL configurada.
Ahora, ejecute la aplicación y verifique manualmente si todo está bien. A continuación se muestra la captura de pantalla de la aplicación de muestra WebView :
Ahora, abra el archivo ExampleInstrumentedTest.java y agregue la siguiente regla:
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class, false, true) {
@Override
protected void afterActivityLaunched() {
onWebView(withId(R.id.web_view_test)).forceJavascriptEnabled();
}
};
Aquí, encontramos WebView y habilitamos JavaScript de WebView porque el marco de pruebas web de espresso funciona exclusivamente a través del motor JavaScript para identificar y manipular elementos web.
Ahora, agregue el caso de prueba para probar nuestro WebView y su comportamiento.
@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")));
}
Aquí, la prueba se realizó en el siguiente orden,
encontró el enlace, Apple usando su atributo id a través del método findElement () y la enumeración Locator.ID .
comprueba el texto del enlace utilizando el método webMatches ()
realiza una acción de clic en el enlace. Abre la página apple.html .
nuevamente encontré el elemento h1 usando métodos findElement () y la enumeración Locator.TAG_NAME .
finalmente vuelve a comprobar el texto de la etiqueta h1 usando el método webMatches () .
Finalmente, ejecute el caso de prueba usando el menú contextual de Android Studio.
En este capítulo, aprenderemos cómo probar operaciones asincrónicas usando Espresso Idling Resources.
Uno de los desafíos de la aplicación moderna es brindar una experiencia de usuario fluida. Proporcionar una experiencia de usuario fluida implica mucho trabajo en segundo plano para asegurarse de que el proceso de solicitud no demore más de unos pocos milisegundos. La tarea en segundo plano va desde la simple hasta la costosa y compleja tarea de obtener datos de una API / base de datos remota. Para enfrentar el desafío en el pasado, un desarrollador solía escribir tareas costosas y de larga duración en un hilo de fondo y sincronizar con el UIThread principal una vez que se completa el hilo de fondo.
Si desarrollar una aplicación multiproceso es complejo, escribir casos de prueba para ella es aún más complejo. Por ejemplo, no deberíamos probar un AdapterView antes de que se carguen los datos necesarios desde la base de datos. Si la obtención de los datos se realiza en un hilo independiente, la prueba debe esperar hasta que se complete el hilo. Por lo tanto, el entorno de prueba debe sincronizarse entre el hilo de fondo y el hilo de la interfaz de usuario. Espresso proporciona un excelente soporte para probar la aplicación multiproceso. Una aplicación utiliza subprocesos de las siguientes formas y espresso admite todos los escenarios.
Subprocesos de la interfaz de usuario
El SDK de Android lo utiliza internamente para proporcionar una experiencia de usuario fluida con elementos de interfaz de usuario complejos. Espresso soporta este escenario de forma transparente y no necesita ninguna configuración ni codificación especial.
Tarea asincrónica
Los lenguajes de programación modernos admiten la programación asíncrona para realizar subprocesos ligeros sin la complejidad de la programación de subprocesos. La tarea asincrónica también es compatible de forma transparente con el marco de espresso.
Hilo de usuario
Un desarrollador puede iniciar un nuevo hilo para obtener datos grandes o complejos de la base de datos. Para respaldar este escenario, espresso proporciona un concepto de recurso inactivo.
Aprendamos el concepto de recurso inactivo y cómo hacerlo en este capítulo.
Visión general
El concepto de recurso inactivo es muy simple e intuitivo. La idea básica es crear una variable (valor booleano) cada vez que se inicia un proceso de larga ejecución en un hilo separado para identificar si el proceso se está ejecutando o no y registrarlo en el entorno de prueba. Durante la prueba, el corredor de pruebas verificará la variable registrada, si encuentra alguna, y luego encontrará su estado de ejecución. Si el estado de ejecución es verdadero, el corredor de prueba esperará hasta que el estado sea falso.
Espresso proporciona una interfaz, IdlingResources, con el fin de mantener el estado de ejecución. El método principal para implementar es isIdleNow (). Si isIdleNow () devuelve verdadero, espresso reanudará el proceso de prueba o esperará hasta que isIdleNow () devuelva falso. Necesitamos implementar IdlingResources y usar la clase derivada. Espresso también proporciona algunas de las implementaciones integradas de IdlingResources para facilitar nuestra carga de trabajo. Son los siguientes,
CountingIdlingResource
Esto mantiene un contador interno de la tarea en ejecución. Expone los métodos increment () y decrement () . increment () agrega uno al contador y decrement () elimina uno del contador. isIdleNow () devuelve verdadero solo cuando no hay ninguna tarea activa.
UriIdlingResource
Esto es similar a CounintIdlingResource, excepto que el contador debe ser cero durante un período prolongado para tomar también la latencia de la red.
InactivoThreadPoolExecutor
Esta es una implementación personalizada de ThreadPoolExecutor para mantener el número de tareas activas en ejecución en el grupo de subprocesos actual.
InactivoScheduledThreadPoolExecutor
Esto es similar a IdlingThreadPoolExecutor , pero también programa una tarea y una implementación personalizada de ScheduledThreadPoolExecutor.
Si se usa alguna de las implementaciones anteriores de IdlingResources o una personalizada en la aplicación, también debemos registrarla en el entorno de prueba antes de probar la aplicación usando la clase IdlingRegistry como se muestra a continuación,
IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
Además, se puede eliminar una vez que se completen las pruebas como se muestra a continuación:
IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
Espresso proporciona esta funcionalidad en un paquete separado, y el paquete debe configurarse como se muestra a continuación en app.gradle.
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Aplicación de muestra
Creemos una aplicación simple para enumerar las frutas obteniéndola de un servicio web en un hilo separado y luego probémosla usando el concepto de recurso inactivo.
Inicie Android Studio.
Cree un nuevo proyecto como se discutió anteriormente y asígnele el nombre MyIdlingFruitApp
Migrar la aplicación a AndroidX marco usando Refactor → Migrar a AndroidX menú de opciones.
Agregue la biblioteca de recursos inactiva de espresso en la aplicación / build.gradle (y sincronícela) como se especifica a continuación,
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Elimine el diseño predeterminado en la actividad principal y agregue ListView. El contenido de activity_main.xml es el siguiente,
<?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>
Agregue un nuevo recurso de diseño, item.xml para especificar la plantilla de elementos de la vista de lista. El contenido del item.xml es el siguiente,
<?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"
/>
Cree una nueva clase: MyIdlingResource . MyIdlingResource se utiliza para mantener nuestro IdlingResource en un solo lugar y buscarlo cuando sea necesario. Vamos a utilizar CountingIdlingResource en nuestro ejemplo.
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 una variable global, mIdlingResource de tipo CountingIdlingResource en la clase MainActivity como se muestra a continuación,
@Nullable
private CountingIdlingResource mIdlingResource = null;
Escriba un método privado para obtener la lista de frutas de la web como se muestra a continuación,
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;
}
Cree una nueva tarea en el método onCreate () para recuperar los datos de la web utilizando nuestro método getFruitList seguido de la creación de un nuevo adaptador y configurándolo en la vista de lista. Además, disminuya el recurso inactivo una vez que nuestro trabajo se complete en el hilo. El código es el siguiente,
// 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.
}
}
}
Aquí, la URL de la fruta se considera http: // <su dominio o IP / fruits.json y está formateada como JSON. El contenido es el siguiente,
[
{
"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 el archivo en su servidor web local y utilícelo.
Ahora, busque la vista, cree un nuevo hilo pasando FruitTask , incremente el recurso inactivo y finalmente inicie la tarea.
// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
El código completo de MainActivity es el siguiente,
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;
}
}
Ahora, agregue la siguiente configuración en el archivo de manifiesto de la aplicación, AndroidManifest.xml
<uses-permission android:name = "android.permission.INTERNET" />
Ahora, compile el código anterior y ejecute la aplicación. La captura de pantalla de la aplicación My Idling Fruit es la siguiente:
Ahora, abra el archivo ExampleInstrumentedTest.java y agregue ActivityTestRule como se especifica a continuación,
@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"
}
Agregue un nuevo caso de prueba para probar la vista de lista como se muestra a continuación,
@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());
}
Finalmente, ejecute el caso de prueba usando el menú contextual de Android Studio y verifique si todos los casos de prueba están teniendo éxito.
Android Intent se usa para abrir una nueva actividad, ya sea interna (abriendo una pantalla de detalles del producto desde la pantalla de lista de productos) o externa (como abrir un marcador para hacer una llamada). La actividad de intención interna es manejada de forma transparente por el marco de prueba de espresso y no necesita ningún trabajo específico por parte del usuario. Sin embargo, invocar la actividad externa es realmente un desafío porque sale de nuestro alcance, la aplicación bajo prueba. Una vez que el usuario invoca una aplicación externa y sale de la aplicación bajo prueba, las posibilidades de que el usuario regrese a la aplicación con una secuencia de acción predefinida son bastante menores. Por lo tanto, debemos asumir la acción del usuario antes de probar la aplicación. Espresso ofrece dos opciones para manejar esta situación. Son los siguientes,
destinado a
Esto permite al usuario asegurarse de que se abra la intención correcta desde la aplicación bajo prueba.
pretendiendo
Esto permite al usuario burlarse de una actividad externa como tomar una foto de la cámara, marcar un número de la lista de contactos, etc., y regresar a la aplicación con un conjunto de valores predefinidos (como una imagen predefinida de la cámara en lugar de una imagen real) .
Preparar
Espresso admite la opción de intención a través de una biblioteca de complementos y la biblioteca debe configurarse en el archivo gradle de la aplicación. La opción de configuración es la siguiente,
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
destinado a()
El complemento de intención de Espresso proporciona comparadores especiales para verificar si la intención invocada es la intención esperada. Los emparejadores proporcionados y el propósito de los emparejadores son los siguientes,
hasAction
Esto acepta la acción de intención y devuelve un comparador, que coincide con la intención especificada.
hasData
Esto acepta los datos y devuelve un comparador, que hace coincidir los datos proporcionados con la intención al invocarla.
empacar
Esto acepta el nombre del paquete de la intención y devuelve un comparador, que coincide con el nombre del paquete de la intención invocada.
Ahora, creemos una nueva aplicación y probemos la aplicación en busca de actividad externa usando () para comprender el concepto.
Inicie Android Studio.
Cree un nuevo proyecto como se discutió anteriormente y asígnele el nombre IntentSampleApp.
Migre la aplicación al marco de AndroidX usando Refactor → Migrar al menú de opciones de AndroidX .
Cree un cuadro de texto, un botón para abrir la lista de contactos y otro para marcar una llamada cambiando el activity_main.xml como se muestra a continuación,
<?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>
Además, agregue el elemento siguiente en el archivo de recursos strings.xml ,
<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
Ahora, agregue el siguiente código en la actividad principal ( MainActivity.java ) bajo el 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
}
Aquí, hemos programado el botón con id, call_contact_button para abrir la lista de contactos y el botón con id, botón para marcar la llamada.
Agregue una variable estática REQUEST_CODE en la clase MainActivity como se muestra a continuación,
public class MainActivity extends AppCompatActivity {
// ...
private static final int REQUEST_CODE = 1;
// ...
}
Ahora, agregue el método onActivityResult en la clase MainActivity como se muestra a continuación,
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);
}
}
};
// ...
}
Aquí, onActivityResult se invocará cuando un usuario regrese a la aplicación después de abrir la lista de contactos usando el botón call_contact_button y seleccionar un contacto. Una vez que se invoca el método onActivityResult , obtiene el contacto seleccionado por el usuario, busca el número de contacto y lo establece en el cuadro de texto.
Ejecute la aplicación y asegúrese de que todo esté bien. El aspecto final de la aplicación de muestra de intención es como se muestra a continuación,
Ahora, configure la intención de espresso en el archivo gradle de la aplicación como se muestra a continuación,
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
Haga clic en la opción de menú Sincronizar ahora proporcionada por Android Studio. Esto descargará la biblioteca de prueba de intención y la configurará correctamente.
Abrir ExampleInstrumentedTest.java archivo y añadir el IntentsTestRule en lugar de utilizado normalmente AndroidTestRule . IntentTestRule es una regla especial para manejar las pruebas de intención.
public class ExampleInstrumentedTest {
// ... code
@Rule
public IntentsTestRule<MainActivity> mActivityRule =
new IntentsTestRule<>(MainActivity.class);
// ... code
}
Agregue dos variables locales para configurar el número de teléfono de prueba y el nombre del paquete del marcador como se muestra a continuación,
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
}
Solucione los problemas de importación utilizando la opción Alt + Enter proporcionada por Android Studio o bien incluya las siguientes declaraciones de importación,
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.*;
Agregue el siguiente caso de prueba para probar si el marcador se llama correctamente,
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
}
Aquí, los comparadores hasAction , hasData y toPackage se utilizan junto con allOf matcher para tener éxito solo si se pasan todos los comparadores.
Ahora, ejecute ExampleInstrumentedTest a través del menú de contenido en Android Studio.
la intención ()
Espresso proporciona un método especial: la intención () de simular una acción de intención externa. intending () acepta el nombre del paquete de la intención que se va a simular y proporciona un método respondWith para establecer cómo se debe responder la intención simulada como se especifica a continuación,
intending(toPackage("com.android.contacts")).respondWith(result);
Aquí, respondWith () acepta el resultado de la intención de tipo Instrumentation.ActivityResult . Podemos crear una nueva intención de código auxiliar y establecer manualmente el resultado como se especifica a continuación,
// 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);
El código completo para probar si una aplicación de contacto se abre correctamente es el siguiente:
@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)));
}
Aquí, hemos creado una nueva intención y establecemos el valor de retorno (al invocar la intención) como la primera entrada de la lista de contactos, contenido: //com.android.contacts/data/1 . Luego, hemos establecido el método de intención para simular la intención recién creada en lugar de la lista de contactos. Establece y llama a nuestro intent recién creado cuando se invoca el paquete com.android.contacts y se devuelve la primera entrada predeterminada de la lista. Luego, activamos la acción click () para iniciar el intento simulado y finalmente verificamos si el número de teléfono que invoca el intento simulado y el número de la primera entrada en la lista de contactos son los mismos.
Si falta algún problema de importación, solucione esos problemas de importación utilizando la opción Alt + Enter proporcionada por Android Studio o, de lo contrario, incluya las siguientes declaraciones de importación,
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.*;
Agregue la siguiente regla en la clase de prueba para otorgar permiso para leer la lista de contactos:
@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);
Agregue la siguiente opción en el archivo de manifiesto de la aplicación, AndroidManifest.xml -
<uses-permission android:name = "android.permission.READ_CONTACTS" />
Ahora, asegúrese de que la lista de contactos tenga al menos una entrada y luego ejecute la prueba usando el menú contextual de Android Studio.
Android admite pruebas de interfaz de usuario que involucran más de una aplicación. Consideremos que nuestra aplicación tiene una opción para pasar de nuestra aplicación a la aplicación de mensajería para enviar un mensaje y luego volver a nuestra aplicación. En este escenario, el marco de prueba del automatizador de UI nos ayuda a probar la aplicación. El automatizador de interfaz de usuario puede considerarse un buen compañero para el marco de prueba de espresso. Podemos aprovechar la opción intending () en el marco de prueba de espresso antes de optar por el automatizador de interfaz de usuario .
Instrucción de configuración
Android proporciona un automatizador de IU como un complemento independiente. Debe configurarse en la aplicación / build.gradle como se especifica a continuación,
dependencies {
...
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Flujo de trabajo para escribir un caso de prueba
Entendamos cómo escribir un caso de prueba basado en UI Automator ,
Obtenga el objeto UiDevice llamando al método getInstance () y pasando el objeto Instrumentation .
myDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
myDevice.pressHome();
Obtenga el objeto UiObject usando el método findObject () . Antes de usar este método, podemos abrir la aplicación uiautomatorviewer para inspeccionar los componentes de la interfaz de usuario de la aplicación de destino, ya que comprender la aplicación de destino nos permite escribir mejores casos de prueba.
UiObject button = myDevice.findObject(new UiSelector()
.text("Run")
.className("android.widget.Button"));
Simule la interacción del usuario llamando al método de UiObject . Por ejemplo, setText () para editar un campo de texto y haga clic en () para activar un evento de clic de un botón.
if(button.exists() && button.isEnabled()) {
button.click();
}
Finalmente, verificamos si la interfaz de usuario refleja el estado esperado.
Escribir un caso de prueba es un trabajo tedioso. Aunque espresso proporciona una API muy fácil y flexible, escribir casos de prueba puede ser una tarea lenta y lenta. Para superar esto, Android Studio proporciona una función para grabar y generar casos de prueba de espresso. Record Espresso Test está disponible en el menú Ejecutar .
Grabemos un caso de prueba simple en nuestra HelloWorldApp siguiendo los pasos que se describen a continuación,
Abra el estudio de Android seguido de la aplicación HelloWorldApp .
Haga clic en Ejecutar → Grabar prueba de Espresso y seleccione MainActivity .
La captura de pantalla de la grabadora es la siguiente:
Haga clic en Agregar afirmación . Abrirá la pantalla de la aplicación como se muestra a continuación,
Haga clic en ¡Hola mundo! . La pantalla de la grabadora para seleccionar la vista de texto es la siguiente:
De nuevo, haga clic en Guardar afirmación. Esto guardará la afirmación y la mostrará de la siguiente manera,
Haga clic en Aceptar . Se abrirá una nueva ventana y preguntará el nombre del caso de prueba. El nombre predeterminado es MainActivityTest
Cambie el nombre del caso de prueba, si es necesario.
Nuevamente, haga clic en Aceptar . Esto generará un archivo, MainActivityTest con nuestro caso de prueba registrado. La codificación completa es la siguiente,
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, ejecute la prueba usando el menú contextual y verifique si el caso de prueba se ejecutó.
La experiencia positiva del usuario juega un papel muy importante en el éxito de una aplicación. La experiencia del usuario no solo implica hermosas interfaces de usuario, sino también la rapidez con que se procesan esas hermosas interfaces de usuario y cuál es la velocidad de fotogramas por segundo. La interfaz de usuario debe ejecutarse de manera constante a 60 fotogramas por segundo para brindar una buena experiencia de usuario.
Aprendamos algunas de las opciones disponibles en Android para analizar el rendimiento de la interfaz de usuario en este capítulo.
dumpsys
dumpsys es una herramienta incorporada disponible en el dispositivo Android. Genera información actual sobre los servicios del sistema. dumpsys tiene la opción de volcar información sobre una categoría en particular. Pasar gfxinfo proporcionará información de animación del paquete suministrado. El comando es el siguiente,
> adb shell dumpsys gfxinfo <PACKAGE_NAME>
framestats
framestats es una opción del comando dumpsys. Una vez dumpsys se invoca con framestats , se volcará información detallada del tiempo de trama de las tramas recientes. El comando es el siguiente,
> adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats
Genera la información como CSV (valores separados por comas). La salida en formato CSV ayuda a enviar fácilmente los datos a Excel y posteriormente extraer información útil a través de fórmulas y gráficos de Excel.
systrace
systrace también es una herramienta incorporada disponible en el dispositivo Android. Captura y muestra los tiempos de ejecución de los procesos de la aplicación. systrace se puede ejecutar usando el siguiente comando en la terminal de Android Studio,
python %ANDROID_HOME%/platform-tools/systrace/systrace.py --time=10 -o
my_trace_output.html gfx view res
La función de accesibilidad es una de las funciones clave para cualquier aplicación. La aplicación desarrollada por un proveedor debe ser compatible con las pautas de accesibilidad mínima establecidas por el SDK de Android para ser una aplicación útil y exitosa. Seguir el estándar de accesibilidad es muy importante y no es una tarea fácil. El SDK de Android proporciona un gran soporte al proporcionar vistas diseñadas correctamente para crear interfaces de usuario accesibles.
De manera similar, el marco de prueba de Espresso hace un gran favor tanto para el desarrollador como para el usuario final al admitir de manera transparente las funciones de prueba de accesibilidad en el motor de prueba central.
En Espresso, un desarrollador puede habilitar y configurar las pruebas de accesibilidad a través de la clase AccessibilityChecks . El código de muestra es el siguiente,
AccessibilityChecks.enable();
De forma predeterminada, las comprobaciones de accesibilidad se ejecutan cuando realiza cualquier acción de visualización. La comprobación incluye la vista en la que se realiza la acción, así como todas las vistas descendientes. Puede verificar la jerarquía de vista completa de una pantalla usando el siguiente código:
AccessibilityChecks.enable().setRunChecksFromRootView(true);
Conclusión
Espresso es una gran herramienta para que los desarrolladores de Android prueben su aplicación por completo de una manera muy fácil y sin poner esfuerzos adicionales que normalmente requiere un marco de prueba. Incluso tiene grabadora para crear casos de prueba sin escribir el código manualmente. Además, admite todo tipo de pruebas de interfaz de usuario. Al utilizar el marco de prueba de espresso, un desarrollador de Android puede desarrollar con confianza una aplicación de gran apariencia, así como una aplicación exitosa sin ningún problema en un corto período de tiempo.