Professional Documents
Culture Documents
1
Perch? Che cosa? Quando?
GOAL: software con zero difetti
MA impossibile da ottenere e garantire
Necessaria una attenta e continua
VERIFICA
Tutto deve essere verificato: documenti
di specifica, di progetto, dati di
collaudo, .programmi
Si fa lungo tutto il processo di sviluppo,
NON solo alla fine!
2
Terminologia
Verifica (verification): insieme delle attivita volte
a stabilire se il programma costruito soddisfa le
specifiche (non solo funzionali)
did we build the program right?
si assume che le specifiche esprimano in modo esauriente i
desiderata del committente
Testing: particolare tipo di verifica sperimentale
fatta mediante esecuzione del programma,
selezionando alcuni dati di ingresso e valutando
i risultati
3
Terminologia
Debugging: localizzare errori (difetti) nel
codice (il testing ne rivela la presenza ma non
li localizza)
Programmazione difensiva: insieme di
tecniche di programmazione che cercano di
evitare di introdurre errori, aumentano
probabilit di correttezza e facilitano verifica e
debugging
5
Testing
Testing esaustivo (esecuzione per tutti i possibili
ingressi) dimostra la correttezza
p.es. se programma calcola un valore in base a un valore di
ingresso nel range 1..10, testing esaustivo consiste nel
provare tutti i valori: per le 10 esecuzioni diverse si verifica
se il risultato quello atteso
impossibile da realizzare in generale:
p.es. se programma legge 3 ingressi interi nel range 1..1000
e calcola un valore, testing esaustivo richiede 109 esecuzioni!
per programmi banali si arriva a tempi di esecuzione pari al
tempo passato dal big-bang
6
Testing
Program testing can be used to show
the presence of bugs, but never to
show their absence. (Dijkstra 1972)
Obiettivo: trovare dati di test che
massimizzino la probabilit di trovare
errori
7
Criteri di test
cruciale la scelta di opportuni valori (dati o
casi di test) "sufficienti a convincerci" che il
programma corretto
p.es.: eseguire il programma con i valori 1, 10 e 5.
In base a che cosa si determinano i casi di
test?
in base alla specifica (test black-box o funzionale;
esiste anche un test white/glass-box, di cui qui non
parliamo)
e quando?
idealmente, nel momento in cui si scrive la
specifica del modulo
In base a quali criteri? 8
Testing funzionale
Propriet
esprime il punto di vista di chi vuole/deve
usare il modulo
esito del test comprensibile da chi non
conosce limplementazione
Varie tecniche per generare dati di test a
partire da specifiche
9
Test funzionale: combinazioni
proposizionali (1)
ESEMPIO
Combinare i vari casi alternativi espressi da una specifica
Es. static int maxOfThree (int x, int y, int z) {
//@ ensures \result == x && x>=y && x>=z ||
\result == y && y>=x && y>=z ||
\result == z && z>=x && z>=y
Ci sono tre alternative: il massimo x, y, o z
Casi di test ricavabili da ensures:
Un caso in cui il massimo x, p. es. (5,3,0)
Un caso in cui il massimo y, p. es. (7,11,2)
Un caso in cui il massimo z, p. es. (7,10,12)
static boolean isPrime (int x)
//@ensures (* \result <==> x primo*)
Scegliere dati di test primi e non primi. Es. 5 e 8
10
Test funzionale:
combinazioni proposizionali (2)
In generale, possiamo identificare le parti in alternativa
di una specifica espressa come formula di logica
proposizionale usando loperatore ||
static float sqrt (float x, float epsilon)
//@requires x >= 0 && .00001 < epsilon < .001 ;
//@ensures x-epsilon <= \result * \result <= x+epsilon ;
REQUIRES congiunzione di x >= 0 con .00001<epsilon<.001
Parte x >= 0 equivalente a x=0 || x>0
Combinazioni ottenibili:
1. x = 0 && .00001 < epsilon < .001
2. x > 0 && .00001 < epsilon < .001
11
Il metodo seguito
Si esamina la clausola REQUIRES
Si partiziona il dominio di ingresso in
sottoinsiemi come specificato da essa,
riducendo la formula proposizionale in forma
x1&&x2&&. || y1&&y2&& ym ||
NB: metodo applicabile a clausole REQUIRES
ma non sempre a clausola ENSURES
in questo caso non conoscendo codice impossibile
prevedere se \result*\result <x o =x o >x
12
Testing funzionale (cont.)
Altre volte possibile e necessario usare
clausola ENSURES
static boolean isPrime (int x)
//@ ensures returns true iff x prime
Scegliere dati di test primi e non primi
13
Casi eccezionali
Testare non solo il comportamento normale ma anche le
eccezioni
//@ensures a!=null &&
//@ (\exists int i; 0<=i && i<a.length; x==a[i]) && a[\result]==x;
//@ signals (NotFoundException e)
//@ (\forall int i; 0<=i && i<a.length; x != a[i]);
//@ signals (NullPointerException e) a == null
static int search (int [] a, int x)
throws NotFoundException, NullPointerException
16
Esempio
//@ensures (*\result il massimo fra x, y, z *)
static int maxOfThree (int x, int y, int z)
17
Errori di aliasing
Due parametri si riferiscono a due oggetti
mutabili, dello stesso tipo
Considerare casi in cui coincidono, anche se non
previsto esplicitamente dalle specifiche
//@ensures(* removes all elements of v2 and appends
//@them in reverse order to the end of v1 *)
19
Specifica di IntSet
public class IntSet {
/*OVERVIEW: insiemi di interi illimitati e modificabili; per es.: {1, 2, 10, -55} */
//costruttori:
//@ensures (\forall int y;;!this.isIn(y));
public IntSet(){ }
//metodi mutators:
//@ ensures this.isIn(x) && (\forall int y; x!=y;
//@ \old(this.isIn(y)) <==> this.isIn(y));
public void insert(int x){ }
//@ ensures !this.isIn(x) && (\forall int y; x!=y;
//@ \old(this.isIn(y)) <==> this.isIn(y));
public void remove(int x){ }
//@ensures (*\result true sse x fra gli elementi di this*);
public boolean isIn (int x){}
//@ ensures (*\result cardinalit di this *);
public int size(){}
20
Caso di studio: Test di IntSet
Aggiungere repOk()
quando si esce da IntSet(), remove(), insert()
Testing funzionale:
valori limite: generare IntSet con 0, 1 o 2 el.
per ognuno testare isIn (risultato false e true), size,
elements
testare size dopo insert e remove, con aggiunta o
cancellazione di elemento presente o assente
testare elements per insiemi di 0, 1, 2, elementi
21
Test delle gerarchie di tipi (1)
22
Test di unit e di integrazione
Test di unit
verifica di un singolo modulo isolatamente
Test di integrazione
verifica di corretta interazione dei moduli
Test di integrazione pi difficile
comportamento da testare pi complesso
maggiore dimensione del codice
spesso interazione poco/mal specificata, e
moduli di tipo e provenienza disomogenea
Conviene prima test di unit e poi test di integrazione (divide et impera)
23
Test di unit
JUnit
24
Esecuzione dei test
Quando si testa un programma importante
definire esattamente i risultati attesi (si parla
di oracolo)
Si pu automatizzare sia l'esecuzione dei test
che il controllo dei risultati (Junit)
Junit (http://junit.org/index.htm)
si basa sull'idea "first testing then coding"
"test a little, code a little, test a little,
25
Junit: esempio 1
import junit.framework.*; (1)
public class SimpleTest extends TestCase { (2)
public SimpleTest(String name) { (3)
super(name);
}
public void testSimpleTest() { (4)
int answer = 2;
assertEquals((1+1), answer); (5)
}
}
26
Spiegazioni
(1) importazione delle classi definite da Junit
(2) va ridefinita la classe TestCase
(3) costruttore del nostro specifico test case:
ha un nome, per identificare un metodo da
impiegare (vedi pi avanti...)
(4) definizione di uno specifico test interno al
test case
(5) il test verifica che "1+1" produca il
risultato definito dall'oracolo
27
Esempio 2
Test di una funzione che dovrebbe
eliminare tutte le "a" da una stringa
28
Esempio 3 (1)
// Adds up a string based on the ASCII values of its
// characters and then returns the binary representation sum
public class BinString {
public BinString() {}
public String convert(String s) {
return binarise(sum(s));
}
public int sum(String s) {
if(s=="") return 0;
if(s.length()==1) return ((int)(s.charAt(0)));
return ((int)(s.charAt(0)))+sum(s.substring(1));
}
public String binarise(int x) {
if(x==0) return "";
if(x%2==1) return "1"+binarise(x/2);
return "0"+binarise(x/2);
}
}
29
Esempio 3 (2)
import junit.framework.*;
public class BinStringTest extends TestCase {
private BinString binString;
public BinStringTest(String name) {
super(name);
}
protected void setUp() {
binString = new BinString(); 1
}
public void testSumFunction() {
int expected = 0; 2
assertEquals(expected, binString.sum(""));
expected = 100;
assertEquals(expected, binString.sum("d"));
expected = 265;
assertEquals(expected, binString.sum("Add"));
}
30
Esempio 3 (3)
public void testBinariseFunction() { 3
String expected = "101";
assertEquals(expected, binString.binarise(5));
expected = "11111100";
assertEquals(expected, binString.binarise(252));
}
public void testTotalConversion() {
String expected = "1000001"; 4
assertEquals(expected, binString.convert("A"));
}
}
31
Spiegazioni
1 setUp (da ridefinire) viene chiamato automaticamente
prima della valutazione di ogni test;
esiste anche tearDown da ridefinire per riportarsi
in condizioni che evitino interferenze tra test
2 test della funzione ausiliaria sum
3 test della funzione ausiliaria binarise
4 test della funzione convert
32
Ancora JUnit
Test definiti tramite luso della famiglia di
ASSERTXXX()
assertTrue()
assertFalse()
assertEquals()
fail()
...
possibile eseguire una Suite di test:
istanziare un oggetto di tipo TestSuite;
aggiungere i test alla suite invocando il metodo
addTest(Test) sull'oggetto istanziato
33
Nuovo esempio
public class Triangolo {
...
35
...
36
Test di regressione
Scenario
programma testato con dati di test da 1 a
n senza trovare errori
trovato errore con dato (n+1)-simo
debugging e correzione del programma
prosecuzione del test con dato (n+2)-simo
Probabilit non trascurabile che la
correzione introduca errori che non lo
fanno funzionare per qualche dato da 1
a n.
37
Test di regressione (cont.)
Consiste nel testare di nuovo il
programma, dopo una modifica, con
tutti i dati di test usati fino a quel
momento, per verificare che non si ha
una regressione
Necessario, ma realizzabile ed
economico in pratica solo se il testing
almeno in parte automatizzato
38
Debugging (1)
Trovare il difetto del programma che d origine a
comportamento erroneo rivelato dal testing
Tecniche di debugging riconducibili a due tipi di
azioni
identificare causa effettiva usando dati di test pi semplici
possibili
localizzare porzione di codice difettoso osservando stati
intermedi della computazione
NB: costo del debugging (spesso contabilizzato
sotto la voce: testing) pu essere parte
preponderante del costo di sviluppo: molto
importante sviluppare il software in modo sistematico
per minimizzare sforzo speso in debugging
39
Debugging (2)
Debugging attivita difficile da rendere sistematica,
efficienza dipende da persone ed poco prevedibile,
MA occorre cercare di essere sistematici
Identificare almeno uno stato corretto S1 e uno non corretto S2
Cercare di capire quali stati intermedi tra S1 e S2 sono corretti e
quali no, fino a identificare uno stato corretto S1 e uno non
corretto S2 consecutivi
Il difetto nellistruzione che separa S1 e S2
41
Programmazione difensiva (1)
Un pizzico di paranoia pu essere utile:
scrivere i programmi in modo che
scoprano e gestiscano ogni possibile
situazione anomala:
procedure chiamate con parametri attuali
scorretti,
file: devono essere aperti ma sono chiusi,
devono aprirsi e non si aprono
riferimenti a oggetti null, array vuoti
Meccanismo delle eccezioni utile aiuto
42
Programmazione difensiva (2)
Essere scrupolosi con il test
ricordarsi che l'obiettivo trovare gli errori,
non essere contenti di non trovarne
testare in particolare
le clausole REQUIRES
gli invarianti di rappresentazione
codificare metodo repOK, testarlo allinizio di ogni
operazione e prima di restituire i risultati
pu convenire dare ad altri il compito di
testare i propri programmi
43
REQUIRES o eccezioni?
//@requires x <= y
//@ensures a!=null &&
//@ (\result <==> (\exists int i; x<=i && i<=y; e==a[i] ) )
//@signals (NullPointerException e) a==null
static boolean inRange (int [] a, int x, int y, int e)
throws NullPointerException
Se chiamata di inRange scambia secondo e
terzo parametro, implementazione diretta
potrebbe non accorgersene e restituire false
durante il test aggiungere nel codice di inRange
controllo che x<=y e sollevare eccezione apposita
in realt potrebbe essere vantaggioso eliminare REQUIRES
e lasciare permanentemente eccezione
44
Controllare tutti i casi
Pu essere molto costoso, ma va fatto quando
possibile
Esempio: ricevibili due soli comandi: deliver o
examine: il codice
s = Comm.receive();
if (s.equals(deliver)) { // execute deliver}
else if (s.equals(examine)) {//execute examine}
else { // gestisci errore }
Molto meglio e poco meno efficiente di
s = Comm.receive();
if (s.equals(deliver)) { // execute deliver}
else { //execute examine }
45
Trade-offs
Talvolta controllo troppo costoso: se una
procedura di ricerca binaria controlla che
insieme di ricerca sia ordinato perde
efficienza
Alternativa per controlli molto costosi: usarli
solo in fase di test e debugging (permettono
di diminuire i costi della ricerca guasti) e
toglierli (con attenzione e cautela,
trasformandoli in commenti) quando il
programma va in produzione
46