Come accennato nella lezione precedente, possiamo prevedere l'uso di unit test su un nuovo progetto di Android Studio come segue:
- inserendo nel modulo applicativo la dipendenza a JUnit per la compilazione dei test:
dependencies { ... testCompile 'junit:junit:4.12' ... }
- predisponendo apposite classi all'interno della cartella java
La classe predisposta per i test unitari sarà simile alla seguente (il cui contenuto ha qui carattere esemplificativo):
public class ExampleUnitTest {
@Test
public void addition_test() throws Exception {
assertEquals(4, 2+2);
}
}
Si tratta di una classe Java, il cui unico metodo ha due caratteristiche particolari:
- l'annotazione
@Test
, messa a disposizione da JUnit per indicare che quel metodo rappresenta un test (possiamo usare tale annotazione in quanto abbiamo incluso JUnit4: fino alla versione 3, invece, i metodi di questo genere avevano un nome con prefisso "test"); - il metodo
assertEquals
, che implementa il testo vero e proprio. In questo esempio, tale test consiste nel confrontare due valori, e si considera passato se essi risultano uguali. In generale, il primo parametro è il valore che ci si aspetta ottenere, mentre il secondo parametro generalmente invoca un metodo o alcune operazioni annidate, il cui risultato dovrebbe essere uguale al primo.
Per eseguire il test, su Android Studio dovremo agire tramite menu:
Al termine dell'esecuzione, Android Studio mostrerà il risultato.
Dall'immagine qui riportata, emerge che il test è stato eseguito su Java Virtual Machine, è durato 2 ms ed è stato portato a termine correttamente: tra l'altro, il successo del collaudo ci viene visivamente confermato anche dal colore verde della banda. Inseriamo ora un test destinato a fallire, aggiungendo il metodo seguuente:
@Test
public void test_boolean() throws Exception {
assertTrue(false);
}
Usiamo un'altra delle assertion di JUnit, ovvero assertTrue
, che verifica se il valore passato corrisponde al true
booleano. Poichè passiamo false
come parametro, il test fallirà e Android Studio mostrerà un risultato simile al seguente:
Ci viene segnalato che due test sono stati eseguiti ed uno è fallito. Anche i colori associati ai singoli test hanno un significato particolare:
- verde: il test ha avuto successo;
- giallo: l'esecuzione non ha riscontrato problemi ma il test non ha avuto successo perchè, ad esempio, il valore ottenuto è diverso da quello atteso;
- rosso: durante il test è fallita l'esecuzione del codice, per esempio per via di in un'eccezione non gestita.
Unit test in un progetto Android
Supponiamo di avere un'app in cui l'interfaccia grafica permetta di registrare dati personali di individui. Per ognuno di essi, vengono raccolti una serie di informazioni - nome, cognome, età e se è automunito - e salvati in oggetti appartenenti alla classe Persona, definita come segue:
public class Persona {
private String nome;
private String cognome;
private int eta;
private boolean automunito;
// OMISSIS: metodi getter e setter
}
Tramite il form dell'interfaccia grafica, creiamo degli oggetti Persona
e li inseriamo nella classe DataManager
, che li custodisce in un ArrayList
in maniera non persistente:
public class DataManager {
private ArrayList elenco=null;
void nuovoInserimento(Persona nuovo)
{
elenco.add(nuovo);
}
int numeroInserimenti()
{
return elenco.size();
}
void cancellaPersona(int pos)
{
if (numeroInserimenti()>0 && pos<elenco.size())
elenco.remove(pos);
}
}
Il funzionamento dell'app è fortemente legato a quello di DataManager
, classe che opera in maniera autonoma dialogando con il resto dell'applicazione mediante l'interfaccia offerta dai propri metodi. Questa è una situazione ideale per svolgere un test unitario. Aggiungiamo i metodi necessari all'interno della classe ExampleUnitTest
che Android Studio ha già predisposto. Creeremo due test: il primo, test_inserimento
, inserirà tre oggetti fittizi nella classe DataManager
e ne verificherà il numero; il secondo, test_cancellazione
, effettuerà la stessa verifica ma dopo averne cancellato uno:
public class ExampleUnitTest {
@Test
public void test_inserimento() throws Exception {
DataManager dm = new DataManager();
dm.nuovoInserimento(new Persona("Giulio", "Rossi", 34, true));
dm.nuovoInserimento(new Persona("Paolo", "Verdi", 25, false));
dm.nuovoInserimento(new Persona("Silvio", "Bianchi", 63, true));
assertEquals(3, dm.numeroInserimenti());
}
@Test
public void test_cancellazione() throws Exception {
DataManager dm = new DataManager();
dm.nuovoInserimento(new Persona("Giulio", "Rossi", 34, true));
dm.nuovoInserimento(new Persona("Paolo", "Verdi", 25, false));
dm.nuovoInserimento(new Persona("Silvio", "Bianchi", 63, true));
dm.cancellaPersona(1);
assertEquals(2, dm.numeroInserimenti());
}
}
L'immagine seguente mostra che i test hanno entrambi successo:
Proviamo a modificare in maniera erronea la classe DataManager
, cambiando l'operatore di confronto all'interno del costrutto if
:
void cancellaPersona(int pos)
{
// metodo sbagliato!
if (numeroInserimenti()<0)
elenco.remove(pos);
}
L'esecuzione del medesimo test rileverà quanto segue:
Come possiamo vedere, uno dei due test sarà errato in quanto il valore ottenuto dall'invocazione del metodo numeroInserimenti
è 3 (visto che la cancellazione non ha funzionato), mentre quello atteso è 2.
È facile immaginare che sarà necessario pianificare una strategia di test molto più estesa di quelle viste negli esempi di questa lezione, come sarà discusso nelle prossime lezioni.