Test e validazione
1
La fase di test
Il test è il processo di verifica di un programma con lo scopo di individuare errori
prima della consegna all’utente finale. Tipicamente, correggere i difetti sul software
evidenziati dopo il rilascio è molto svantaggioso in termini d’immagine e soprattutto
economici Il software deve quindi essere testato e collaudato per la ricerca di errori
commessi durante la fase di progetto e di realizzazione.
Normalmente, secondo Glenford J. Myers, un programmatore più o meno esperto
individua solo il 50% delle categorie di test necessarie. Questo dimostra che il test è
tutt’altro che banale e va affrontato in maniera sistematica, possibilmente
automatizzato e soprattutto documentato. Lo scopo di questa fase, a cui si dedica a
seconda delle risorse tra il 30% e il 40% della durata dell’intero progetto (quasi la
metà), è proprio quello di ridurre il numero di errore nel software a un livello
accettabile; in questo aiuta l’idea che un test fallisce quando non trova errori e che un
programmatore non debba testare i propri moduli.
L’obiettivo di questo documento è quello di descrivere il piano di test del software
adottato per il collaudo del sistema “Monopoli” definendo quali sono stati i moduli
testati, le tecniche utilizzate e l’analisi dei risultati ottenuti.
1.1 Tecniche
Esistono varie tecniche per test del software, di queste alcune fanno uso di un
calcolatore, altre tengono conto semplicemente di situazioni comuni che
tendono a verificarsi di cui è utile tenerne conto per avere una prima
scrematura dei difetti più evidenti.
Queste sono le principali tecniche utilizzate in questo progetto:
• Ispezione: Questa tecnica viene tipicamente impiegata subito dopo la fine
della codifica e va considerata preliminare al test automatizzato. Il
programmatore, aiutandosi con una lista di errori ricorrenti (checklist),
ispeziona il codice da testare alla ricerca soprattutto di: errori di calcolo, errori
di confronto, errori di interfaccia...; per questo motivo si parla di “test umano”.
• Test white box: Il test white box (scatola bianca) è un test strutturale che si
esegue guardando dentro il codice da testare, per questo può essere progettato
soltanto quando il codice è disponibile. Un approccio topologico per il progetto
di test a scatola bianca si basa sul grafo di controllo del programma composto
da un nodo per ogni istruzione atomica ed un arco per ogni transizione. I casi
di test vengono definiti da determinati cammini sul grafo seguendo il criterio di
copertura di condizioni e decisioni che permette di ricavare il maggior numero
di errori possibile. Il numero di cammini da cercare deve essere pari alla
complessità ciclomatica, un valore ottenuto da:
CC = Archi – Nodi + 2
Una volta ricavati i cammini, è utile sapere se questi formano una base ovvero
sono tra loro
linearmente indipendenti. Questo si ottiene calcolando il
rango della matrice di incidenza,
in cui sono riportati, per ogni cammino,
tutti gli archi ricoperti, e controllando che sia
massimo.
• Test black box: Il test black box (scatola nera) è un test funzionale che testa la
logica del programma, complementare al test white-box e assolutamente non
alternativo. Il dominio di input e di output della funzione viene partizionato in
classi di equivalenza: una serie di insiemi di dati che auspicabilmente
provocano lo stesso comportamento, e possono essere valide o non valide. Le
intersezioni tra le classi possono essere sfruttate tra classi valide e testarle con
un solo input, mentre vanno evitate, ammesso che sia possibile (inclusione) per
le classi non valide perché è importante sapere dove ricade l’errore. I criteri da
seguire per individuare le classi di equivalenza sono: intervalli di valori,
numero di valori, insiemi di valori, condizioni vincolanti. Per la sua natura,
questo tipo di test può e va progettato prima della codifica, appena i requisiti
sono stabili, in modo da programmare sapendo quali difetti ci si aspetta che
vengano trovati.
Abbiamo deciso di non fare il test walkthrow onestamente per mancanza di
tempo ma anche e soprattutto perché ci siamo accorti di avere affrontato (forse
sbagliando) lo sviluppo dell’applicazione in maniera abbastanza corale
coinvolgendo tutti i membri in praticamente tutti i moduli del programma. Per
questo sarebbe un po’ venuto meno lo scopo di questo lavoro basato proprio
sul fatto che vada ispezionato codice altrui senza conoscerne la struttura e il
contenuto.
1.2 Strategie
Il principio di una buona strategia per il collaudo deve avere prove a basso
livello per verificare che un piccolo segmento di codice sorgente sia stato
realizzato in maniera corretta e prove ad alto livello capaci di assicurare che i
requisiti utente siano stati soddisfatti a pieno.
Le fasi seguite sono state rispettivamente:
• Revisione: La fase propedeutica al test intensivo è quella di revisionare il
codice attraverso ispezione e test umano. La checklist che abbiamo utilizzato è
la seguente:
1.
2.
3.
4.
5.
Correttezza dei commenti
Errori di interfaccia tra pagine web e servlet, tra servlet e JSP…
Errori di cast
Errori di interfaccia tra i moduli
Ridurre la ridondanza
Questo tipo di lavoro è stato svolto da ognuno di noi durante
l’implementazione su proprie porzioni di codice stabile.
• Test di unità: Il test di unità verifica ciascun modulo separatamente,
assicurandone il corretto funzionamento come unità distinte. Si adatta bene ad
un assemblaggio incrementale e fa largo uso delle tecniche white box e black
box.
• Test di integrazione: Il test di integrazione si specializza sull’interazione tra i
moduli proprio perché molti errori si nascondono nel passaggio scorretto di
parametri e nella errata interpretazione dei valori di ritorno. Per questo tipo di
test è utile avere a disposizione l’albero di chiamate delle funzioni e richiede
che vengano progettati dei moduli aggiuntivi che simulano il calcolo sui valori
di ritorno (stub) e il passaggio corretto di parametri (driver).
• Test di accettazione: Eseguito a valle del test di sistema (che nel nostro caso
non è previsto), il test di accettazione verifica l’effettiva funzionalità del
prodotto simulando un tipico scenario di utilizzo.
• Test di regressione: È la fase che tipicamente troviamo alla fine di ogni step
di test e controlla che tutte le correzioni apportate al codice non abbiano
prodotto altri problemi. Questo si ottiene rieseguendo tutti o parte dei test
definiti nelle fasi precedenti.
Al termine della fase di test il software e pronto per la consegna ed entra a in
manutenzione.
1.3 Tool
I tool di cui ci siamo serviti per implementare i casi di test pianificati sono:
JUnit : JUnit (www.junit.org) è un framework java da utilizzare per il test di
unità. Mette a disposizioni funzioni per inizializzare le strutture dati secondo i
modi previsti e per testare condizioni, valori di ritorno ed eccezioni.
Jakarta Cactus: Cactus (jakarta.apache.org/cactus) è un’estensione di JUnit
per testare oggetti server-side (servlet e JSP). Prevede infatti la possibilità di
definire le strutture dati utilizzate da questi oggetti (es: session, request,
response...) e di verificare la pagina di risposta.
2
Pianificazione ed esecuzione dei test
Si passa ora alla trattazione degli aspetti legati alla pianificazione. La
pianificazione dei test è un concetto che va oltre la definizione di cosa testare e
come testarlo, ovvero di quelle attività che tipicamente vanno sotto il nome di
“piano dei test”; ma si occupa di tutti gli aspetti coinvolti nel progetto dei
test, nella loro esecuzione e nella correzione. Per raggiungere un buon livello di
qualità e di descrizione, è importante stabilire dettagliatamente la strategia
d’azione di come definire e implementare un caso di test, come comportarsi
nell’eseguirlo, come affrontare le correzioni di eventuali errori e cosa fare a
seguito di queste. Tutto ciò deve essere poi supportato da un’equa suddivisione
del lavoro definendo responsabilità e referenti di ogni attività.
2.1
Piano dei test
Il piano dei test ha preso in considerazione soltanto gli scenari e le funzioni più
importanti dell’applicazione, le funzioni sono state scelte però in modo tale da
sfruttare il più possibile analogie strutturali e funzionali in modo da propagare,
entro certi limiti, il test a più componenti. Questo significa però stare più attenti
alle correzioni apportate, lavorando bene con il test di regressione per non
rischiare di allargare possibili malfunzionamenti ad altre unità.
Da notare che tutte queste attività tralasciano il test del codice HTML. Questo
produce problemi soprattutto nell’annidamento di tag e sugli script di controllo.
Gli errori più evidenti sono stati comunque rivisti con il test di accettazione.
2.2
Test black box
Test del metodo login di ControllerUtente
1. Tecnica del test: black box
2. Descrizione
3. Segnatura: boolean login(Utente u)
4. Obiettivo: il metodo controlla se per lo username in questione la password
corrisponde a quella inserita in fase di registrazione
5. Parametri
6. Utente u
7. Valore restituito: booleano (true se la password è corretta)
8. Definizione delle classi d'equivalenza
ID
Classe
Valida/Non Valida
C1
Utente presente sul DB
Valida
C2
Utente = null
Non Valida
C3
Utente non presente sul DB
Valida
1. Definizione dei casi di test
1. Test Case TL1
1. Copre la classe C1
2. Input: utente1
3. Output atteso: true
2. Test Case TL2
1. Copre la classe C2
2. Input: null
3. Output atteso: java.lang.NullPointerException.class
■ N.B. Il caso di un utente = null viene testato ma non si può comunque
presentare poiché i controlli vengono preliminarmente effettuati dal
codice javascript della pagina di registrazione
3. Test Case TL3
1. Copre la classe C3
2. Input: utente2
3. Output atteso: false
Test del metodo insert di DaoUtente
1. Tecnica del test: black box
2. Descrizione
1. Segnatura: void insert (Utente u)
2. Obiettivo: inserire un nuovo utente nella tabella Utente
3. Parametri
1. Utente u
4. Valore restituito: nessun valore
3. Definizione delle classi d'equivalenza
ID
Classe
Valida/Non Valida
z
C1
Utente valido
Valida
C2
Utente = null
Non Valida
Definizione dei casi di test
c Test Case TI1
■ Copre la classe C1
■ Input: utente
■ Output atteso: nessun output
c Test Case TI2
■ Copre la classe C2
■ Input: null
■ Output atteso: RuntimeException.class
■ N.B. Il caso di un utente = null viene testato ma non si può comunque
presentare poiché i controlli vengono preliminarmente effettuati dal
codice javascript della pagina di registrazione
Test del metodo nickPresente di DcsUtente
z Tecnica del test: black box
z Descrizione
c Segnatura: boolean nickPresente (Utente u)
c Obiettivo: il metodo controlla se per lo username in questione l'utente
corrispondente è presente nel DB
c Parametri:
■ Utente u
c Valore restituito: booleano (true se l'utente è presente nel DB)
z Definizione delle classi d'equivalenza
ID
Classi
Valida/Non Valida
z
C1
Utente presente sul DB
Valida
C2
Utente = null
Non Valida
C3
Utente non presente sul DB
Valida
Definizione dei casi di test
c Test Case TN1
■ Copre la classe C1
■ Input: utente1
■ Output atteso: true
c Test Case TN2
■ Copre la classe C2
■ Input: null
■ Output atteso: java.lang.NullPointerException.class
■ N.B. Il caso di un utente = null viene testato ma non si può comunque
presentare poiché i controlli vengono preliminarmente effettuati dal
codice javascript della pagina di registrazione
c Test Case TN3
■ Copre la classe C3
■ Input: utente2
■ Output atteso: false
2.3
Test white box
Servlet ServHome
/*inizio*/ protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
/*1*/
HttpSession sessione=request.getSession(true);
ServletContext sc=getServletContext();
String user=request.getParameter("T4");
String pwd=request.getParameter("T5");
String address = null;
Utente u=ControllerUtente.getUtente(user, pwd);
/*2*/
if(ControllerUtente.login(u)){
/*3*/
if(!ServletUtil.login(u,sc) ){
/*4*/
sc.setAttribute(u.getnick(),u);
sessione.setAttribute("utente",u);
address="Redirect6.htm";
}
else{
/*5*/
sessione.setAttribute("err","Utente Loggato");
address="ERRORIACCESSO.jsp";
}
}
else{
/*6*/
sessione.setAttribute("err","
Nick o Password errati\\no
registrazione non effettuata");
address="ERRORIACCESSO.jsp";
}
/*7*/
/*fine*/
Grafo:
RequestDispatcher dispatcher=request.getRequestDispatcher(address);
dispatcher.forward(request, response);
}
Complessità Ciclomatica: V(G)=3
Cammini Ricoprenti:
9. C1: a-b-c-e
10. C2: a-b-d-f
11. C3: a-g-h
Matrice d'incidenza:
a
b
c
C1
X
X
X
C2
X
X
C3
X
coperto
X
X
d
e
f
g
h
X
X
X
X
X
X
X
X
X
X
X
Casi Testati:
ID
Input
Output
Esito
TSHC1
Utente u є DB, u.getpass() = DcsUtente.getPassword(u), u.getnick() !є contesto
Redirect6.htm
OK
TSHC2
Utente u є DB, u.getpass() = DcsUtente.getPassword(u), u.getnick() є contesto
ERRORIACCESSO.jsp
OK
TSHC3
Utente u є DB, u.getpass() != DcsUtente.getPassword(u)
ERRORIACCESSO.jsp
OK
Servlet ServReg
/*inizio*/ protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
/*1*/
HttpSession sessione=request.getSession(true);
String nome=request.getParameter("T2");
String cognome=request.getParameter("T3");
String email=request.getParameter("T4");
String
nato=(request.getParameter("D2")+"/"+request.getParameter("D3")+"/"+request.getP
arameter("D4"));
String nick=request.getParameter("T5");
String pwd=request.getParameter("T7");
Utente utente= new Utente(nome,cognome,nato,email,nick,pwd);
String address=null;
/*2*/
if(!ControllerUtente.nickPresente(utente)){
/*3*/
if(ControllerUtente.registrazione(utente)){
/*4*/
ControllerUtente.insert(utente);
address="Redirect2.htm";
}
else{
/*5*/
sessione.setAttribute("err","Utente già registrato");
address="ERRORIACCESSO.jsp";
}
}
else{
/*6*/
/*7*/
/*fine*/
sessione.setAttribute("err","Nick già in uso");
address="ERRORIACCESSO.jsp";
}
RequestDispatcher dispatcher=request.getRequestDispatcher(address);
dispatcher.forward(request, response);
}
Grafo:
Complessità Ciclomatica: V(G)=3
Cammini Ricoprenti:
z C1: a-b-c-e
z C2: a-b-d-f
z C3: a-g-h
Matrice d'incidenza:
a
b
c
C1
X
X
X
C2
X
X
C3
X
coperto
X
X
d
e
f
g
h
X
X
X
X
X
X
X
X
X
X
X
Casi Testati:
ID
Input
Output
Esito
TSRC1
u.nick !є DB && tupla <u.nome, u.cognome, u.dataNascita> !є DB
Redirect2.htm
OK
TSRC2
u.nick !є DB && tupla <u.nome, u.cognome, u.dataNascita> є DB
ERRORIACCESSO.jsp
OK
TSRC3
u.nick є DB
ERRORIACCESSO.jsp
OK
Servlet ServSA
/*inizio*/ public void doGet(HttpServletRequest request,HttpServletResponse
response)throws ServletException, IOException {
/*1*/
HttpSession sessione=request.getSession(true);
ServletContext sc=getServletContext();
String Tipo=request.getParameter("D3");
String Pedina=request.getParameter("D4");
int NumGiocatori=Integer.parseInt(request.getParameter("D5"));
String Nome=request.getParameter("D1");
String
Ora=request.getParameter("D6")+":"+request.getParameter("D10")+"
"+request.getParameter("D11");
String
Data=request.getParameter("D7")+"/"+request.getParameter("D8")+"/"+request.getPa
rameter("D9");
String address = null;
Utente u=(Utente)sessione.getAttribute("utente");
sessione.setAttribute("pedina",Pedina);
/*2*/
if ( Tipo.equals("Crea")) {
/*3*/
Partita p=new Partita(Nome);
Semaphore sem= new Semaphore(0);
sem.release();
int count=NumGiocatori-1;
String c2=ServletUtil.codSem(p);
String c1=ServletUtil.codifica(p);
String cod=ServletUtil.codifica(p,Pedina);
/*4*/
if(!ServletUtil.controlloCreazione(cod, sc)){
/*5*/
Contesto con=new Contesto(p,0,u,NumGiocatori);
sessione.setAttribute("game",p);
sc.setAttribute(cod,con);
sc.setAttribute(c2,sem);
sc.setAttribute(c1, count);
address ="Redirect7.htm";
}
else{
/*6*/
sessione.setAttribute("err","Partita già
esistente");
address="ERRORE.jsp";
}
}
/*7*/
else if (Tipo.equals("Partecipa")){
/*8*/
Partita p =new Partita(Nome,Data,Ora);
String c2P=ServletUtil.codSem(p);
String c1P=ServletUtil.codifica(p);
/*9*/
if(ServletUtil.controlloPartecipazione(c2P,sc)&&
ServletUtil.controlloPartecipazione(c1P,sc)){
/*10*/
Semaphore semp;
semp=(Semaphore)sc.getAttribute(c2P);
semp.acquireUninterruptibly();
int count=(Integer) sc.getAttribute(c1P);
/*11*/
if(count!=0){
/*12*/
count=count-1;
String cod=ServletUtil.codifica(p,Pedina);
/*13*/
if(ServletUtil.controlloCreazione(cod,
sc)){
/*14*/
if(!ServletUtil.controlloPartecipazione(cod, sc)){
/*15*/
address ="Redirect11.htm";
sessione.setAttribute("game",p);
Contesto con=new Contesto(p,0,u);
sc.setAttribute(cod,con);
sc.setAttribute(c1P, count);
semp.release();
}
else{
/*16*/
sessione.setAttribute("err","Pedina in uso");
address="ERRORE.jsp";
semp.release();
}
}
else{
/*17*/
sessione.setAttribute("err","Partita non presente");
address="ERRORE.jsp";
semp.release();
}
}
else{
/*18*/
sessione.setAttribute("err","Partita Completa");
address="ERRORE.jsp";
semp.release();
}
}
else{
/*19*/
sessione.setAttribute("err","la partita Non
Esiste");
address="ERRORE.jsp";
}
}
else{
/*20*/
Partita p =new Partita(Nome,Data,Ora);
try {
/*21*/
if(ControllerPartita.partitaPresente(p)&&ServletUtil.verificaLogati(sc,
sessione, p)){
/*22*/
sessione.setAttribute("game", p);
Utente
u2=(Utente)sessione.getAttribute("utente");
String cod=ServletUtil.codificaRip2(p);
/*23*/
if(!ServletUtil.controlloPartecipazione(cod, sc)){
/*24*/
LinkedList<String> list=new
LinkedList<String>();
list.add(u2.getnick());
String
c2=ServletUtil.codificaRip(p);
sc.setAttribute(c2, 0);
sc.setAttribute(cod, list);
address="Redirect12.htm";
}
else{
/*25*/
LinkedList<String>
list=(LinkedList<String>) sc.getAttribute(cod);
list.add(u2.getnick());
address="Redirect15.htm";
}
}
else{
/*26*/
sessione.setAttribute("err","la
partita Non Esiste o non vi puoi partecipare");
address="ERRORE.jsp";
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*27*/
RequestDispatcher
dispatcher=request.getRequestDispatcher(address);
dispatcher.forward(request, response);
}
Grafo:
Complessità Ciclomatica: V(G)=10
Cammini Ricoprenti:
2. C1: a-b1-c1-d1-f1
3. C2: a-b1-c1-e1-g1
4. C3: a-b-c2-d2-e2-f2-g2-h2-i2-l2-m2
5. C4: a-b-c2-d2-e2-f2-g2-h2-i2-u2-n2
6. C5: a-b-c2-d2-e2-f2-g2-h2-o2-p2
7. C6: a-b-c2-d2-e2-f2-q2-r2
8. C7: a-b-c2-d2-s2-t2
9. C8: a-b-c3-d3-e3-f3-g3-h3
10. C9: a-b-c3-d3-e3-f3-i3-l3
11. C10: a-b-c3-d3-m3-n3
Matrice d'incidenza:
a
b
b1
c1
c2
c3
d1
d2
d3
e1
e2
e3
f1
f2
f3
g1
g2
g3
h2
h3
i2
i3
l2
l3
m2 m3 n2
n3
o2
p2
q2
r2
s2
t2
u2
C1
X
X X
C2
X
X X
C3
X X
X
X
X
X
X
X
X
C4
X X
X
X
X
X
X
X
X
C5
X X
X
X
X
X
X
X
C6
X X
X
X
X
X
C7
X X
X
X
C8
X X
X
X
X
X
C9
X X
X
X
X
X
C10
X X
X
X
coper
to
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
X
X
X
X
X
X
X
X
X X
X X
X X
X
X
X
X
X
X
X
Casi Testati:
ID
Input
Output
Esito
TSSAC1
Tipo = “Crea”, Partita.p !є al contesto
Redirect7.htm
OK
TSSAC2
Tipo = “Crea”, Partita.p є al contesto
ERRORE.jsp
OK
TSSAC3
Tipo = “Partecipa”, (semaphore && count) є al contesto, count != 0, Partita p є contesto, Redirect11.htm
pedina !є contesto
OK
TSSAC4
Tipo = “Partecipa”, (semaphore && count) є al contesto, count != 0, Partita p є contesto, ERRORE.jsp
pedina є contesto
OK
TSSAC5
Tipo = “Partecipa”, (semaphore && count) є al contesto, count != 0, Partita p !є contesto ERRORE.jsp
OK
TSSAC6
Tipo = “Partecipa”, (semaphore && count) є al contesto, count = 0
ERRORE.jsp
OK
TSSAC7
Tipo = “Partecipa”, (semaphore && count) !є al contesto
ERRORE.jsp
OK
TSSAC8
Tipo != “Crea” || “Partecipa”, (Partita p є DB && (Utente.nome,Partita p) є DB) ==
true, Partita.p !є contesto
Redirect12.htm
OK
TSSAC9
Tipo != “Crea” || “Partecipa”, (Partita p є DB && (Utente.nome,Partita p) є DB) ==
true, Partita.p є contesto
Redirect15.htm
OK
TSSAC10
Tipo != “Crea” || “Partecipa”, (Partita p є DB && (Utente.nome,Partita p) є DB) ==
false
ERRORE.jsp
OK
2.4 Note Cactus
Difficile installazione nell’ambiente di lavoro (Eclipse), ci ha
portato ad un ritardo considerevole nella consegna del progetto
Scarica

Fase di Test