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:
Nivel: Intermedio – Avanzado
Este post, esta dividido en 2 partes:
1. Creación de la aplicación objetivo
Datos Generales:
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
<?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.
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.
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
Realizaremos 4 test sobre nuestra aplicación:
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
Pasemos a explicar el código de uno de los test linea por linea:
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
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
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!!!
Java Network Launching Protocol (JNLP) es una especificación usada por Java Web Start. Esta especificación, permite tene[...]
Si bien se puede hacer uso de archivos de imagen (jpg, png, bmp, etc) en las aplicaciones java, también este te permite[...]
Hace tiempo pidieron un video tutorial sobre como crear sus propios componentes swing java, lamentablemente debo decir q[...]
En un post anterior se vio como llenar un JTree en donde se conocía de antemano que estructura iba a tener esta sin emba[...]
Cuando Android estaba en sus inicios, varios IDEs (Entorno de Desarrollo Integrado) se disputaban el dominio por su de[...]
En este videotutorial veremos la forma de crear un ejecutable para programas hechos en lenguaje Java con el software Jav[...]