Sigueme en Facebook Sigueme en Twitter Sigueme en Instagram Sigueme en Youtube
JC Mouse Bolivia
Index / Android / Pruebas instrumentadas con Espresso

Pruebas instrumentadas con Espresso

Autor jc mouse martes, septiembre 18, 2018

Espresso es un framework de testing propiedad de Google que está dirigido a desarrolladores que creen que las pruebas automatizadas son una parte integral del ciclo de vida del desarrollo de software. Si bien se puede utilizar para pruebas de caja negra, todos los que están familiarizados con el código bajo prueba pueden aprovechar el verdadero potencial de Espresso.

Las Pruebas Instrumentadas nos permiten poner a prueba nuestro código junto a la Interfaz de Usuario (UI sigla en ingles) simulando interacciones de los usuarios y los diferentes escenarios con los que el usuario final puede encontrarse y de esta manera detectar y corregir el mal funcionamiento de nuestra aplicación antes de lanzar este a producción y así evitar malas experiencias a los usuarios.

En este Post desarrollaremos un pequeño testing a una aplicación android el cual es un formulario de login.

Necesitamos:

  • Android Studio 3.x
  • De preferencia un dispositivo físico móvil (Emulador también sirve)

Nivel: Intermedio – Avanzado

Este post, esta dividido en 2 partes:

  1. Creación de la aplicación objetivo
  2. Realización del testing con Espresso

1. Creación de la aplicación objetivo

Datos Generales:

  • Nombre de la aplicación: EspressoFoo (El nombre es lo de menos, no me juzgen)
  • Dominio: example.org
  • Actividad principal (y única): LoginActivity
  • Layout: activity_login.xml
  • Permisos: Ninguno
  • Base de datos: No necesita

Descripción: Nuestra aplicación objetivo consiste en un formulario de autenticación de usuario el cual realiza un par de validaciones con los datos introducidos por el usuario, si estos son incorrectos, notifica con un mensaje de error.

Los datos a validar son (1) que tanto el nombre de usuario y la contraseña son obligatorios (2) la longitud mínima para el nombre de usuario debe ser mayor a cuatro caracteres. Si cumple con estos requisitos, procede a la autenticación del usuario, si los datos de autenticación son correctos o no, la app notificara con un mensaje.

Dependencias:

    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:cardview-v7:27.1.1'
    implementation 'com.android.support:design:27.1.1'

Layout: activity_login.xml

blueprint

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    android:background="@android:color/holo_blue_light"
    tools:context=".LoginActivity">

    <android.support.v7.widget.CardView
        android:id="@+id/cardView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="24dp"
        android:layout_marginLeft="24dp"
        android:layout_marginRight="24dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_margin="8dp"
            android:orientation="vertical">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <android.support.design.widget.TextInputEditText
                    android:id="@+id/etUsuario"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="6dp"
                    android:hint="Usuario"
                    android:inputType="text"
                    android:maxLength="12"
                    android:maxLines="1" />
            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <android.support.design.widget.TextInputEditText
                    android:id="@+id/etPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="Contraseña"
                    android:inputType="textPassword"
                    android:maxLines="1"
                    android:maxLength="24" />

            </android.support.design.widget.TextInputLayout>
        </LinearLayout>
    </android.support.v7.widget.CardView>

    <Button
        android:id="@+id/btnIngresar"
        android:layout_width="336dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="24dp"
        android:text="Ingresar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/cardView" />

    <TextView
        android:id="@+id/tvError"
        android:layout_width="336dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:gravity="center_vertical|center_horizontal"
        android:text="TextView"
        android:textColor="@android:color/holo_red_dark"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnIngresar" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="68dp"
        android:text="LOGIN"
        android:textColor="@android:color/white"
        android:textSize="36sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Clase: LoginActivity.java

package example.org.espressofoo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputEditText;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class LoginActivity extends AppCompatActivity {

    private Button btnIngresar;
    private TextInputEditText etUsuario;
    private TextInputEditText etPassword;
    private TextView tvError;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        getSupportActionBar().hide();
        btnIngresar = findViewById(R.id.btnIngresar);
        etUsuario = findViewById(R.id.etUsuario);
        etPassword = findViewById(R.id.etPassword);
        tvError = findViewById(R.id.tvError);

        tvError.setText("");
        tvError.setVisibility(View.INVISIBLE);
        btnIngresar.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                login(v);
            }
        });
    }//onCreate:end

    /**
     * Autentica al usuario (sin base de datos)
     * Usuario: mouse
     * Password: 123456
     * */
    private void login(View view){
        if(datosValidos()){
            if(etUsuario.getText().toString().equals("mouse")
                    && etPassword.getText().toString().equals("123456")){
                /**
                 * Aqui pasariamos a otro activity
                 * Pero solo notificamos que el Acceso a la aplicación, esta autorizado
                 * */
                tvError.setText("No hay error");
                Snackbar.make(view,
                        "Datos correctos. Acceso autorizado.", Snackbar.LENGTH_SHORT)
                        .show();
            }else{
                mostrarError("Los datos son incorrectos. Intenta nuevamente.");
            }
        }
    }

    /**
     * Valida los datos ingresados por el usuario, si estos son incorrectos
     * notifica con un mensaje
     *
     * @return boolean TRUE si los datos son correctos, FALSE caso contrario
     * */
    private boolean datosValidos(){

        if(etUsuario.getText().toString().isEmpty() || etPassword.getText().toString().isEmpty()){
            mostrarError("Todos los datos son obligatorios");
            return false;
        }

        if( etUsuario.getText().length()<=4){
            mostrarError("Longitud de nombre de usuario no es valida.");
            return false;
        }

        tvError.setVisibility(View.INVISIBLE);
        tvError.setText("");
        tvError.setAlpha(1.0f);
        return true;
    }

    /**
     * Mensaje de alerta para el usuario, dura 2.5 segundos antes de desaparecer
     *
     * @param mensaje
     * */
    private void mostrarError(String mensaje){
        tvError.setVisibility(View.VISIBLE);
        tvError.setText(mensaje);
        tvError.animate()
                .alpha(0.0f)
                .setDuration(2600).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                tvError.setVisibility(View.INVISIBLE);
                tvError.setAlpha(1.0f);
            }
        });
    }

}//LoginActivity:end

Ejecuta la aplicación con tu dispositivo o en el emulador de Android Studio y observa como funciona.

login user

2. Testing con espresso

Las Pruebas Instrumentadas en Android Studio se alojan en el paquete marcado como (androidTest) creado por defecto con cualquier proyecto, este paquete cuenta con una clase prueba “ExampleInstrumentedTest” en el cual podemos escribir nuestro código de prueba o eliminarlo y crear nuestro propio archivo.

tester

Antes de escribir el código para testear nuestra app, debemos dirigirnos al archivo build.gradle (Module: App) y verificar si las siguientes dependencias están presentes:

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

Si no están presentes, debes agregarlos.

A continuación en el paquete androidTest, creamos una nueva clase con el nombre de TestLogin.java

test rule

Realizaremos 4 test sobre nuestra aplicación:

  • Acceso Autorizado: Colocaremos como usuario: mouse y como password: 123456, dado que los datos son correctos, no debe producirse ningún mensaje de error.
  • Datos Incorrectos: En este test se introducen datos no validos y por tanto la app no concederá el acceso y se notifica al usuario mediante el mensaje “Los datos son incorrectos. Intenta nuevamente.”
  • Longitud de usuario invalido: Se estableció como mínimo 5 caracteres para el nombre de usuario,  por tanto al introducir una cadena de 4 caracteres o menos, se espera un mensaje de la forma “Longitud de nombre de usuario no es valida.”
  • Datos Obligatorios: Tanto el nombre de usuario como la contraseña son obligatorios, por tanto si uno de estos datos o ambos no son introducidos, se espera un mensaje del tipo “Todos los datos son obligatorios”

A continuación el código completo de la clase TestLogin.java

package example.org.espressofoo;

import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.support.test.rule.ActivityTestRule;
//espresso
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class TestLogin {

    @Rule
    public ActivityTestRule<LoginActivity> mActivityRule = new ActivityTestRule<>(LoginActivity.class);

    @Test
    public void accesoAutorizado(){
        onView(withId(R.id.etUsuario))
                .perform(typeText("mouse"), closeSoftKeyboard());
        onView(withId(R.id.etPassword))
                .perform(typeText("123456"), closeSoftKeyboard());
        onView(withId(R.id.btnIngresar)).perform(click());
        onView(withId(R.id.tvError)).check(matches(withText("No hay error")));
    }

    @Test
    public void datosIncorrectos(){
        onView(withId(R.id.etUsuario))
                .perform(typeText("raton"), closeSoftKeyboard());
        onView(withId(R.id.etPassword))
                .perform(typeText("654321"), closeSoftKeyboard());
        onView(withId(R.id.btnIngresar)).perform(click());
        onView(withId(R.id.tvError)).check(matches(withText("Los datos son incorrectos. Intenta nuevamente.")));
    }

    @Test
    public void longitudUsuarioInvalido(){
        onView(withId(R.id.etUsuario))
                .perform(typeText("rata"), closeSoftKeyboard());
        onView(withId(R.id.etPassword))
                .perform(typeText("654321"), closeSoftKeyboard());
        onView(withId(R.id.btnIngresar)).perform(click());
        onView(withId(R.id.tvError)).check(matches(withText("Longitud de nombre de usuario no es valida.")));
    }

    @Test
    public void datosObligatorios(){
        onView(withId(R.id.etUsuario))
                .perform(typeText(""), closeSoftKeyboard());
        onView(withId(R.id.etPassword))
                .perform(typeText("abcdefghj"), closeSoftKeyboard());
        onView(withId(R.id.btnIngresar)).perform(click());
        onView(withId(R.id.tvError)).check(matches(withText("Todos los datos son obligatorios")));
    }

}

Los métodos usados en esta clase son propios de Espresso y puedes encontrarlos en la documentación oficial de desarrolladores de Android (Espresso cheat sheet) o descargarlo en formato PDF espresso-cheat-sheet-2.1.0.pdf

chear sheet

Pasemos a explicar el código de uno de los test linea por linea:

codigo comentado

Testing:

Para iniciar el testeo de la aplicación, dirígete a la parte superior derecha del editor, selecciona la clase TestLogin y presiona el botón RUN que esta a su derecha y espera

run app

Las pruebas se irán ejecutando una después de la otra, si obtienes un error en algún test el proceso se detendrá caso contrario habrás pasado el test con éxito

all test passed

Hasta aquí el post, desarrollamos un ejemplo sencillo de una prueba instrumentada con Android Studio y Espresso, las pruebas pueden ser muchos más y más complejas, todo depende del tester (probador de software)

enjoy!!!

Tags

Si te ha gustado podrías compartirlo o dejar un comentario. ¡Muchas gracias!
Autor: JC Mouse

Yo soy yo :) JC Mouse, Soy orgullosamente boliviano soy fundador y CEO de la web jc-Mouse.net uno de las pocas web en emprendimiento y tecnología en Bolivia.

Toda la información que encuentres en este sitio es y sera completamente gratis siempre, puedes copiar, descargar y re-publicar si así lo deseas en otros blogs o sitios web, solo te pido a cambio que dejes una referencia a esta web. Esto nos ayuda a crecer y seguir aportando. Bye

Enjoy! :)

También Te Podría Interesar

Formulario de login personalizado

Formulario de login personalizado

Hola, en este oportunidad dejo a disposición un formulario de logueo que se me ocurrió de repente creo inspirado en las...

Gestión de productos – El modelo (Parte 4)

Gestión de productos – El modelo (Parte 4)

En la estructura del proyecto, nuestro modelo mvc esta compuesto por tres paquetes, la vista, el modelo y controlador. L...

Incrustar reporte en formulario java

Incrustar reporte en formulario java

Jasperreport nos brinda su propio visor de reportes, sin embargo en ocasiones queremos mostrar los informes directamente...

HSQLDB: Gestor de bases de datos libre en java

HSQLDB: Gestor de bases de datos libre en java

HyperSQL DataBase (HSQLDB) es un moderno administrador de bases de datos relacionales, es portátil e implementado en jav...

Capturar webcam con VB.NET

Capturar webcam con VB.NET

¿Que haremos? Crearemos una aplicación en Visual Studio, la cual hará uso de una webcam para capturar el video en frames...

Puzzle en Visual Basic [Código Fuente]

Puzzle en Visual Basic [Código Fuente]

Rompecabezas 3×3 realizado en Visual Basic .NET, lo que se hizo fue una migración de un proyecto hecho en java (pue...

Comparte lo que sabes

Categorias

Últimas entradas

Los números narcisistas o números de Armstrong son aquellos números enteros positivos  cuya suma de las n-ésimas potenci...

En la convención de Chain React  en Portland (Oregon), Marc Horowitz ingeniero de software en Facebook presentó el motor...

En un post anterior [Introducción a VueJS framework para el desarrollo FrontEnd] realizamos una breve introducción a Vue...

La programación y la electrónica están a la orden del día. Cada vez son más importantes y prácticamente forman parte de...

Android Bolivia

MAUS