Il testing è applicato isolatamente ad una unità (modulo) di un sistema software.
Obiettivo fondamentale è quello di rilevare errori (logica e dati) nel modulo.
Motivazioni:
Unità: elemento definito nel progetto di un sistema software e testabile separatamente unità e modulo sono spesso usati come sinonimi.
Per eseguire un test case su una unità (o una combinazione di unità) è necessario che questa sia isolata dal resto del sistema.
E’ quindi necessario scrivere del codice aggiuntivo per permettere ad un’unità di eseguire in isolamento.
Test driver e test stub sono usati per sostituire le parti mancanti del sistema.
Un test driver (o Modulo Guida) è un blocco di codice che inizializza e chiama la componente da testare.
Un test stub (o Modulo Fittizio) è una implementazione parziale di componenti da cui la componente testata dipende (componenti che sono chiamate dalla componente da testare).
Espone le stesse signatures dei moduli simulati, ma è più semplice
L’insieme del Test driver e di tutti i test stub forma lo Scaffolding, cioè l’ambiente per eseguire test case su un’unità isolata.
Intuitivamente, ogni qual volta una unità da testare contiene un riferimento ad un’altra classe/modulo, questa va “simulata”, al fine di fare test in isolamento.
La simulazione è effettuata attraverso la scrittura di un codice fittizio, contenuta in un test stub.
Un test stub deve fornire la stessa API del metodo della componente simulata e ritornare un valore il cui tipo è conforme con il tipo del valore di ritorno specificato nella signature.
Se l’interfaccia di una componente cambia, anche il corrispondente test stub deve cambiare.
L’implementazione di un test stub non è una cosa semplice.
Creare l’ambiente per l’esecuzione dei test è un’attività non banale.
Lo scaffolding è estremamente importante per il test di unità e integrazione.
Può richiedere un grosso sforzo programmativo.
Uno scaffolding buono è un passo importante per test di regressione efficiente.
La generazione di scaffolding può essere parzialmente automatizzato a partire dalle specifiche …
Esistono pacchetti software per generare scaffolding: JUnit, NUnit, PUnit, etc…
Junit è un framework di unit testing per Java.
Autori: Erich Gamma, Kent Beck.
Cosa è un JUnit Test?
Un test “script” è un insieme di metodi Java.
Cosa offre JUnit? Gli Assert.
Un esito è il risultato dell’esecuzione di un singolo test.
Successo (rappresentato con colore verde): il test case è andato a buon fine, e il software testato funziona come previsto
Fallito (rappresentato con colore rosso): il test case è andato a buon fine, ma il software testato non fuziona come previsto
Errore (rappresentato con colore blu): il test case non è andato a buon fine.
Le cause possono essere:
Il codice di un test basato su JUnit permette di fare:
JUnit offre una serie di metodi per determinare se un metodo è eseguito correttamente o meno.
Questi sono i metodi assert, che permettono di confrontare il risultato della computazione di un metodo da testare con l’output predetto dall’oracolo.
Gli assert sono il cuore del framework Junit
Gli assert sono definiti nella classe Junit Assert.
Tutti i metodi assert sono statici.
Le condizioni booleane sono vere o false
assertTrue(condition)
assertFalse(condition)
Gli oggetti sono null o non-null
assertNull(object)
assertNotNull(object)
Gli oggetti sono identici (i.e. due riferimenti allo stesso oggetto), o non identici.
assertSame(expected, actual)
Vero se : expected == actual
assertNotSame(expected, actual)
“Uguaglianza” di oggetti:
assertEquals(expected, actual)
valido se: expected.equals( actual )
“Uguaglianza” di array:
assertArrayEquals(expected, actual)
gli array hanno la stessa lunghezza
per ogni valore valido di i, controlla a seconda dei casi:
assertEquals(expected[i],actual[i])
oppure
assertArrayEquals(expected[i],actual[i])
Vi è anche un assert di fallimento incondizionato fail()
, che restituisce sempre un esito di fallimento.
assertEquals(a,b)
si basa sul metodo equals()
della classe testata.
a.equals( b )
.equals()
dalla classe Object
otterranno il comportamento equals()
di default.Se a
e b
sono di un tipo primitivo come, per esempio, int
, boolean
, etc., allora per il metodo assertEquals(a,b)
viene effettuato quanto segue:
a
e b
sono convertite nei loro oggetti equivalenti (Integer
, Boolean
etc.,) e viene valutato su di essi a.equals( b ).
Quando si confrontano i tipi floating point (double
o float
), c’è un ulteriore parametro obbligatorio, delta
.
L’assert valuta Math.abs( expected – actual )← delta
per evitare errori di round-off nei casi di confronti tra tipi floating point.
Esempio:
assertEquals( aDouble, anotherDouble, 0.0001 )
In ogni metodo di assert con due parametri, il primo parametro rappresenta il valore atteso, mentre il secondo parametro è il valore reale.
Qualsiasi metodo di assert può avere un ulteriore parametro di tipo String
come primo parametro. Il valore di tale parametro verrà incluso nel messaggio che comparirà se l’assert fallisce.
Esempio:
fail( message )
assertEquals( message, expected, actual)
Aggiungere un parametro all’annotation @Test
, indicando che ci si aspetta una particolare classe di eccezione diurante questo test.
@Test(expected=ExceptedTypeOfException.class)
public void testException()
{
exceptionCausingMethod();
}
Se non viene generata nessuna eccezione, o occorre una eccezione inaspettata, il test fallisce.
Cioè, arrivando alla fine del metodo senza nessuna eccezione causerà il fallimento del test case.
Testare il contenuto di un messaggio di eccezione, o limitare lo scope dell’eccezione utilizzandol’approccio mostrato in figura.
Una classe test può invocare altre classi test creando test suites.
Una qualsiasi classe test può contenere un metodo statico nominato suite()
all’interno del quale è possibile eseguire una qualsiasi collezione di test.
public static Test suite()
JUnit fornisce due metodi che possono essere sovrascritti al fine di poter costruire l’ambiente di testing (ex. inizializzare variabili, effettuare connessioni al database ecc) prima di eseguire tutti i test o pulire (ex. chiudere la connessione al database) dopo che tutti i test sono stati eseguiti:
protected void setUp()
protected void tearDown()
Il metodo setUp verrà chiamato prima che tutti i metodi testxxx siano eseguiti e viene utilizzato ad esempio per inizializzare variabili o stabilire connessioni al database.
Il metodo tearDown verrà chiamato dopo che tutti i metodi testxxx sono stati eseguiti ed esempio per liberare memoria o chiudere connessioni al database.
Tutto quanto visto finora vale per JUnit versione 4.
Per la versione 3.x, abbiamo delle differenze.
Non possiamo utilizzare direttamente i metodi assert nel nostro codice del progetto, ma c’è bisogno di un progetto di test, che si basa su Junit.
Importiamo le necessarie classi JUnit.
Ogni classe che effettua un test eredita da TestCase. La classe base TestCase contiene la maggior parte delle funzionalità necessarie per poter effettuare Unit testing, compresi i metodi assert.
Abbiamo bisogno di un costruttore al quale passiamo una stringa, ed effettuiamo una chiamata al costruttore super.
La classe contiene metodi nominati con test… Tutti i metodi che hanno nomi che iniziano con test… saranno automaticamente eseguiti da Junit 3.x, tutti quelli annotati con @test
saranno eseguiti da Junit 4.
TestCases (classi Java)
Tests (metodi Java) - test di un singolo metodo
Etichettare gli assert.
Eseguire un test separato in ogni metodo testXxxx.
Eseguire i test utilizzando tools: IDE, Ant, Maven…
I test devono essere indipendenti.
Package uguali, directory differenti.
Package uguali, directory differenti
1. Introduzione all'Ingegneria del Software – La qualità del Software
2. Il Ciclo di vita del Software: i modelli di Processo
3. Concetti e strumenti di base per il Project Management
5. Introduzione ad UML: Gli Use Case Diagrams
6. Il documento dei Requisiti Software
7. UML: I Sequence Diagrams ed i Class Diagrams