Testing delle interfacce utente - parte 2

In questa lezione, proseguiremo lo studio di Espresso come meccanismo di test per le interfacce utente. Ne abbiamo già visto i principi fondamentali e sperimentato la capacità di "usare" i controlli visuali in base alle indicazioni da noi fornite. Andremo avanti aggiungendo qualche funzionalità alla nostra app di prova in modo da poter sperimentare ulteriori funzionalità di questo framework.

Cosa sottoporremo a test

L'app da collaudare ha un'Activity principale che contiene una ListView in cui vengono mostrati i dati inseriti relativamente a dei soggetti da registrare. Le due funzionalità che metteremo sotto osservazione grazie ad Espresso sono:

Attiveremo tramite Espresso entrambe queste funzionalità e ne valuteremo gli effetti.

Primo test: inserimento tramite form

Considerando che il form su cui dobbiamo svolgere il test si trova in una finestra di dialogo, possiamo per prima cosa verificare che il Floating Action Button sia correttamente associato ad un'azione che ne inneschi effettivamente l'apertura. Il seguente, breve, test verifica proprio questo: produciamo un click sul pulsante flottante e controlliamo se risulta visibile il layout che compone l'interfaccia utente del form al quale abbiamo assegnato esplicitamente un id, R.id.dialog_layout:

@Test
    public void openDialog()
    {
        onView(withId(R.id.fab)).perform(click());
        onView(withId(R.id.dialog_layout)).check(matches(isDisplayed()));
    }

potremo innestare questo test nella struttura di classe vista nella lezione precedente e lanciarlo.

Il test più completo che abbiamo intenzione di svolgere si articola invece su più fasi:

    @Test
    public void insertData()
    {

	// 1. le stringhe che inseriremo nel form
        String nomePerTest="Enrico";
        String cognomePerTest="Bianchi";
        String etaPerTest="28";

	// 2. cosa ci aspettiamo di leggere nell'ultima riga della ListView
        String stringaAttesa=cognomePerTest+" "+nomePerTest+" - età "+etaPerTest;

	// 3. click sul FAB
        onView(withId(R.id.fab)).perform(click());

	// 4. inserimento testi nel form
        onView(withId(R.id.nome)).perform(typeText(nomePerTest));
        onView(withId(R.id.cognome)).perform(typeText(cognomePerTest));
        onView(withId(R.id.eta)).perform(typeText(etaPerTest));

	// 5. salvataggio mediante click su pulsante "Salva"
        onView(withId(android.R.id.button1)).perform(click());

	// 6. leggiamo la nuova dimensione della ListView (dovrebbe essere aumentata)
        int quanti=((ListView)mIntentActivityRule.getActivity().findViewById(R.id.listView)).getCount();

	// 7. verifichiamo se ciò che c'è scritto nell'ultima riga della ListView equivale a stringaAttesa
        onData(anything()).inAdapterView(withId(R.id.listView))
                .atPosition(quanti-1)
                .onChildView(withId(R.id.testo))
                .check(matches(withText(containsString(stringaAttesa))));

    }

Il test lavora interamente sull'interfaccia utente sia a livello di inserimento sia di verifica però coinvolge i funzionamenti interni dell'Adapter risultando pertanto piuttosto completo.

Secondo test: invocazione di Intent

Al click di una riga della ListView viene aperta una seconda Activity che mostra i dati relativi ad un soggetto: tali informazioni dovranno essere associate all'Intent. Espresso permette di svolgere diversi test sul funzionamento di un Intent e qui ne proveremo alcuni. Per fare ciò però serve, per prima cosa, inserire un'ulteriore libreria tra le dipendenze senza dimenticare di sincronizzare il progetto con i file di configurazione di Gradle:

dependencies {
	...
	androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
	...
}

Fatto questo potremo utilizzare apposite classi per il testing. Osserviamo prima come nell'app di prova funziona il passaggio alla seconda Activity. Il layout che utilizziamo per rappresentare la struttura della riga della ListView ha un attributo onClick che indica quale metodo chiamare quando si esegue un tap su una riga:

<RelativeLayout
    ...
    android:onClick="mostraDettagli">

mentre, all'interno della classe MainActivity, troviamo il codice che attiva l'Intent, non prima di aver recuperato l'oggetto di classe Persona relativo alla riga selezionata e averlo allegato al pacchetto di Extras. Si noti che, per semplicità, si è definita la classe Persona un'implementazione di Serializable:

public class MainActivity extends AppCompatActivity {
    ..
    ..
    public final static String PERSONA_EXTRA="persona_extra";
    ..
    ..
    public void mostraDettagli(View v)
    {
        int pos=listView.getPositionForView(v);
        Persona p=adapter.getItem(pos);
        Intent i=new Intent(this, DetailActivity.class);
        i.putExtra(PERSONA_EXTRA, (Serializable) p);
        startActivity(i);

    }
   ..
   ..
}

All'interno dei test, dovremo:

Vediamo subito un esempio:

public class Intent_Test {

    @Rule
    public IntentsTestRule<MainActivity> mIntentActivityRule = new IntentsTestRule<>(MainActivity.class);

@Test
    public void openNewActivity()
    {

        onData(anything())
           .inAdapterView(withId(R.id.listView))
                .atPosition(0)
                   .perform(click());

        intended(toPackage("it.html.esempio_espresso02"));
    }

}

Con la prima riga, viene eseguito un click sul primo elemento della ListView (che daremo per scontato esista) e successivamente con intended eseguiamo il controllo verificando se l'Activity attivata faccia parte del package it.html.esempio_espresso02.

Altro metodo utile consiste nella verifica della presenza di un Extra caratterizzato da una determinata chiave:

@Test
    public void checkExtra()
    {
        onData(anything())
                .inAdapterView(withId(R.id.listView))
                .atPosition(0)
                .perform(click());
        intended(hasExtraWithKey(MainActivity.PERSONA_EXTRA));
    }

Conclusioni

Come si può immaginare, l'armamentario di strumenti a disposizione di Espresso è molto più ricco di quello che abbiamo visto in queste lezioni ma abbiamo comunque messo in moto meccanismi fondamentali di cui avremo spesso bisogno nei test come inserimento di testo e click su controlli: per il resto si può consultare la documentazione allegata al framework e alle varie librerie in ecco incluse. Si ricordi sempre l'utilità di poter lanciare anche test singoli: sarà sufficiente fare click con il tasto destro del mouse all'interno di un metodo e selezionare il comando Run relativo.

Figura 3. Avvio di un singolo test

Avvio di un singolo test

Inoltre, si cerchi sempre di verificare la bontà di un test non accontentandosi del suo successo ma facendolo anche fallire con dati volutamente errati.