Android/Espresso Test

Introducció

modifica

Què és el testeig d’aplicacions?

modifica

A l'hora de comprovar el correcte funcionament d'una aplicació en la fase de desenvolupament, ens veiem obligats a fer-les de manera individual i interactiva en un principi. No obstant això, que pot ser viable per a aplicacions petites i senzilles, pot tornar-se una tasca molt més tediosa i repetitiva quant més gran i complexa es torna la nostra aplicació. Per tal de poder concentrar els nostres esforços al desenvolupament i treure'ns treball de sobre, el framework Android ens proporciona una sèrie d'eines per a l'automatització d'aquestes tasques de testeig, entre elles l'Espresso.

Espresso és una eina que permet fer el testeig de les teves aplicacions de manera totalment automatitzada, simulant el comportament que tindria una persona normal i corrent. Normalment, aprofitem i anem una mica més enllà per prevenir qualsevol tipus de falla en la sistema que pugui aparèixer per casos no previstos. A més a més, s'ha de tenir en compte que cada conjunt de proves que es vol aplicar a una app, requereix una eina diferent i personalitzada per cadascuna. Aquesta eina es pot implementar gracies a la biblioteca Android Testing Support, fàcilment incorporable al nostre entorn, sempre hi quan, estiguem programant sobre una versió superior o igual al Android 2.2 i amb nivell d’API 8 o superior.

Per què fer servir Espresso?

modifica

Cal destacar que aquesta eina, a diferencia de les altres, actua en sincronització automàtica amb les accions de la interfície que veu el usuari. D’aquesta manera, Espresso “veu” quins son els fils que s’estan executant en cada moment i pot fer una execució més semblant a la que faria una persona a mà.

A l'hora de testejar una aplicació Android, hem de discriminar dos casos diferents:

  • Les proves relatives al correcte funcionament intern de les nostres classes. Aquestes proves no requereixen de cap framework especial i es poden realitzar de manera senzilla amb els Local Unit Test.
  • Les proves relatives a la interacció entre dispositiu i persona. Aquestes sí que requereixen de funcions especials per a produir interaccions simulades i comprovar que aquestes dónen el resultat esperat.

L'Espresso ens proporciona eines per a realitzar tests en ambdós sentits: Ens permet fer proves unitàries, proves d'interacció, i a més, automatitzar-les per a la nostra conveniència.

Exemples d'altres eines de testeig les podem trobar a TestSuite, que ens permet embolcallar un conjunt de proves unitàries: https://developer.android.com/reference/junit/framework/TestSuite.html

També amb IU Automator, que afegeix interaccions externes a la nostra aplicació, tal com prémer el botó de settings del dispositiu, o llançar la llista d'aplicacions: https://developer.android.com/topic/libraries/testing-support-library/index.html#UIAutomator

Una altra eina externa al framework d'Android Studio és Robotium, que implementa les mateixes funcionalitats que Espresso: https://github.com/robotiumtech/robotium

Posta a punt

modifica

Per poder programar la interfície Espresso, abans de tot necessitarem incorporar al nostre entorn de programació les dependències a les biblioteques necessàries. En aquestes biblioteques es troba tota la informació i els mètodes que necessitarem més endavant. A més a més, haurem de preparar una mica l'entorn sobre el que treballarem per evitar possibles errors d'execució, i tenir coneixement de què podem fer en cada moment.

Primer de tot, hem de preparar el nostre entorn de treball, és a dir, fer unes modificacions a la màquina on s'executarà l'aplicació.

Inhabilitació d'animacions

modifica

Es recomana deshabilitar les animacions del nostre dispositiu a l'hora de fer les proves. Si les deixéssim habilitades, podria donar-se el cas de rebre resultats inesperats o que l’aplicació deixés de funcionar. Les opcions que hem de deshabilitar son:

   - Window animation scale
   - Transition animation scale
   - Animator duration scale

Que es troben al menú de Opcions de Desenvolupament del nostre dispositiu Android (NOTA: Per tal d'accedir-hi és possible haver de desbloquejar el menú en primera instància si no ho hem fet ja. Un exemple de tutorial: http://www.greenbot.com/article/2457986/how-to-enable-developer-options-on-your-android-phone-or-tablet.html )

 
Configuració del entorn de treball

Dependències necessàries

modifica

Tot seguit,hem de dirigir-nos al fitxer build.gradle(Module App), que es troba dintre del projecte generat amb AndroidStudio de manera automàtica, i hi incorporarem les sentencies següents en ell. Quedant com a resultat:

    defaultConfig{
        // Other defaultConfig ...
        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
    }
    
    dependencies {
        // Other dependencies ...
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:23.4.0'
        testCompile 'junit:junit:4.12'
    }

Un cop hem afegit aquestes línies, el més probable és que ens demani recompilar/resincronitzar el gradle, ja que acabem d'afegir informació nova i el projecte es vol actualitzar i preparar pels events futurs.

De la classe de testeig

modifica

Un cop tenim ja definits els prerequisitis, ens podem centrar en construir cada part del test. Quan nosaltres volem provar una aplicació amb Espresso, necessitem crear una classe nova dintre del nostre projecte on encapsularem totes les accions de prova que executarem després. Pot rebre el nom que vulguem, però necessitem que estigui a la ruta "C:\\Nom_del_projecte\app\src\androidTest\java\domini_del_projecte\NomTest.java", sinó ens generarà problemes de compilació.

 
Path_androidtest

Dintre d'aquesta nova classe, és on implementarem cadascuna de les proves que farem servir sobre l'aplicació a testejar. Aquesta nova classe, haurà de tenir una estructura similar a la següent:

  • Declaració de les diferents biblioteques que necessitarem (la captura de pantalla correspon a un cas en particular; en cada cas caldrà fer els imports de les funcions utilitzades)
 
Llibreries del projecte
  • Capçalera de la classe, com qualsevol classe Java
 
Capçalera de la classe
  • Etiquetes @Rules, @Before i @Test i variables local si s'escau
 
Etiquetes disponibles
  • @Rules: aquesta etiqueta l'utilitzarem per dir-li al test sobre quina classe volem aplicar les proves que escriurem més endavant.
  • @Before: aquesta etiqueta la farem servir per inicialitzar tot el que necessitem abans que l'aplicació comenci a testejar.
  • @Test: tota funció que tingui aquesta etiqueta serà una prova que executarà l'aplicació de testeig.

Dintre de les funcions amb l'etiqueta @Test, haurem de definir les accions que executarà l'aplicació utilitzant uns mètodes en concret que Espresso ens ofereix de base. Utilitzant-les, podrem tant generar accions com si fóssim l'usuari i comprovar-ne el resultat.

Per poder simular la interacció humana, hem de seguir la següent estructura amb tres parts principals:

  • Identificar la View sobre la que aplicarem les proves
  • Generar l'acció pertinent sobre la View
  • Recuperar el resultat obtingut

Per identificar una View, utilitzarem el mètode onView(), que és qui retornarà la View en qüestió, en conjunt amb el mètode withId() que és l'encarregat de definir quin paràmetre li estem passant al mètode. withId() requereix d'un paràmetre que és l'identificador de la View que estem consultant. Per veure un exemple:

        onView(withId(R.id.edittext_view))

Tot seguit, per generar l’acció del usuari, hem d'utilitzar el mètode ViewInteratcion.perform(), passant-li com a paràmetre l'acció que volem que s'executi. Val a dir, que com a paràmetres també n'accepta més d'un. En la llista següent, podem trobar els diferents mètodes per executar cadascuna de les accions que l'usuari podrà fer dintre de la nostra aplicació. Val a dir que totes estan orientades a una View:

  • ViewActions.click(): simula un click sobre la view
  • ViewActions.typeText(String): simula un click en la view i hi escriu el text especificat per l'string que li passem per paràmetre.
  • ViewActions.scrollTo(): simula com l'usuari mou la visibilitat de la View, de tal manera que es pugui veure la View que estem buscant.
  • ViewActions.pressKey(KeyCode): simula un click en la tecla que li passem per paràmetre en forma de keycode.
  • ViewActions.clearText(): en la view seleccionada se li neteja el text que contingui.

Com a exemple, podem observar aquesta acció que fa click sobre la View en qüestió:

        .perform(typeText("Hola que tal"))

Es pot observar tota la llista completa de mètodes a la pàgina oficial d’Android: https://developer.android.com/reference/android/support/test/espresso/action/ViewActions.html


Com a últim pas, necessitem un mètode per garantir que realment s'ha executat tal i com esperàvem. La manera és utilitzant el mètode ViewInteraction.check(). Aquest mètode serveix per comprovar que el estat esperant concorda amb els resultats esperats. A més a més, requereix d'un paràmetre per dir-li al mètode que és el que ha de comprovar. Els paràmetres disponibles són els següents:

  • doesNotExist: verifica que la View que estem tractant no visió en la vista actual.
  • matches: verifica que la View que estem tractant existeix a la vista actual i que, a més a més, coincideixi amb la cerca que li hem declarat al paràmetre.
  • selectedDescendentsMatch: verifica que existeixen uns fills especificats a partir de la vista del pare, i el seu estat coincideix amb algun del paràmetres especificats.

Per posar un exemple complet, podríem identificar un EditText, cambiar-li el text i comprovar el resultat d'aquesta manera:

    onView(withId(R.id.edittext))
        .perform(typeText("Message"))
        .check(matches(withText("Message"));

Un exemple pràctic

modifica

Preparació de l'entorn

modifica

En aquest punt, explicarem pas a pas com crear i aplicar un exemple pràctic. Abans de tot, haurem de crear un projecte nou sobre el qual treballarem i farem les proves pertinents. Podem omplir aquest projecte amb el contingut que vulguem, Espresso es pot aplicar en qualsevol projecte seguint les normes especificades anteriorment.

 
Menú Inici d'Android Studio

Un cop tenim el projecte, haurem de preparar el nostre build.gradle i crear una classe nova test. La podem anomenar com vulguem, en aquest cas l'anomenarem arrancadacotxeInstrumentationTest. Com a resultat, ens quedarà una estructura com aquesta:

 
Estructura interna de fitxers

Entorn de prova

modifica

Abans de seguir ensenyarem una mica com funciona l'aplicació de prova per poder entendre millor quins comprovacions farà el test.

 
Vista principal

La imatge que podem observar a l'esquerra, és la vista principal que ens apareixerà quan obrim la nostra app de prova. En ella podem observar un toolbar amb diferents botons, però que no compleixen cap funció, dos Botons, dos TextView, que seran l'objectiu a testejar juntament amb els botons, i dues imatges, una d'elles oculta.

Aquesta aplicació serveix per anar mirant quin tipus de cotxe estem fent servir, si estem fent servir un que s'arrenca prement un botó o utilitzant una clau. Això ho podem aconseguir prement el botó Arrenca, cada cop que el premem s'activarà un event que, de manera intercalada, ens triarà un dels dos tipus de cotxe i aplicarà uns canvis a la vista. De manera més detallada, aquest botó té una responsabilitat que consisteix en què quan es premi ha de canviar el text d'ambdós TextView que apareixen en pantalla i, al costat de la fotografia del cotxe vermell apareixerà una altra imatge, depenent del cas serà la imatge d'un botó o d'una clau.

En canvi, el botó Nou Cotxe, té la responsabilitat de re-iniciar les variables que es mostren per pantalla. És a dir, tornar a deixar la vista com es veu en la imatge Vista principal, sense nosaltres tocar el botó.

 
Vista amb el botó actiu
 
Vista amb la clau activa


En les imatges de la dreta i l'esquerra, es pot observar les vistes que es mostren en els dos casos possibles quan premem el botó Arrenca. En el cas del botó, la imatge de l'esquerra, apareix al costat del cotxe vermell una imatge d'un botó lila i en els TextView, es pot apreciar que el text ha canviat. Ara en el primer text diu: "Tipus de cotxe: amb botó" i en el segon diu: "Apa som-hi!!".

En el cas de la clau, la imatge de la dreta, apareix al costat del cotxe vermell una imatge d'una clau grisa i ens TextView, es pot apreciar que el text ha canviat. Ara en el primer text diu: "Tipus de cotxe: amb clau" i en el segon diu: "Apa som-hi!!".





Entorn Espresso

modifica

Ara que ja tenim l'estructura bàsica del nostre projecte i sabem què fa l'aplicació de prova, afegirem cadascuna de les parts necessàries a la classe de Test que hem creat.

  • Capçalera
package edu.upc.damo.arrancadacotxedef;

import android.support.test.rule.ActivityTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
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.withResourceName;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
  • Cos principal
public class arrancadacotxeInstrumentationTest {
    @Rule
    public ActivityTestRule<arrancadacotxe> activityTestRule = new ActivityTestRule<>(arrancadacotxe.class);

    @Before
    public void Ini(){}

    @Test
    public void validate(){}
}

En aquest cas concret, no necessitem inicialitzar cap variable, per tant la funció amb etiqueta @Before el deixarem buit. El que sí que modificarem serà el contingut de la funció amb etiqueta @Test, introduint les línies de codi següent. Cada bloc, correspon a la comprovació del resultat quan es premen els botons pertinents.

onView(withId(R.id.arrenca)).perform(click());
onView(withId(R.id.model)).check(matches(withText("Tipus de cotxe: amb boto")));
onView(withId(R.id.text)).check(matches(withText("Apa som-hi!!")));

onView(withId(R.id.arrenca)).perform(click());
onView(withId(R.id.model)).check(matches(withText("Tipus de cotxe: amb clau")));
onView(withId(R.id.text)).check(matches(withText("Apa som-hi!!")));

onView(withId(R.id.arrenca)).perform(click());
onView(withId(R.id.model)).check(matches(withText("Tipus de cotxe: amb boto")));
onView(withId(R.id.text)).check(matches(withText("Apa som-hi!!")));

onView(withId(R.id.clean)).perform(click());
onView(withId(R.id.model)).check(matches(withText("Tipus de cotxe:")));
onView(withId(R.id.text)).check(matches(withText("Marxem?")));

Tot seguit, podem veure una demostració de vídeo de l'execució completa de tot l'esquema anterior.

A practical example of Android Studio running an Espresso Test on a particular application.

Codi font del projecte d'exemple

modifica

El codi font es pot trobar aquí