You are on page 1of 46

Testing e Debugging

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

Attenzione: terminologia non


standardizzata!
4
Testing
Si fanno esperimenti con il comportamento
del programma allo scopo di scoprire
eventuali errori
si campionano comportamenti
come ogni risultato sperimentale, fornisce
indicazioni parziali relative al particolare
esperimenti
programma provato solo per quei dati
GOAL: trovare "controesempi"
Tecnica dinamica rispetto alle verifiche
statiche fatte dal compilatore

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

Testare search con array a null, con x non in a, con x in a


?? testare anche con input che non soddisfa clausola
REQUIRES??
NO, se metto REQUIRES responsabile il cliente
se invece metto eccezione, devo anche testare la via cha la genera

bene evitare la REQUIRES e definire funzioni totali


14
Testing con valori limite
(boundary values)
Se valore dellinput pu stare in un intervallo,
testare estremi dellintervallo e combinare
valori limite
Esempi:
valori estremi per i numeri (max. int
ammissibile)
sqrt con radicando = 0
stringa: vuota o di 1 carattere
array: array vuoto o di un elemento
elaborazioni con array: considerare valori
estremi degli indici
15
Altri esempi
Triangoli identificati da vertici:
tre punti allineati
due punti coincidenti
tre punti coincidenti
triangolo rettangolo
un vertice nellorigine o sugli assi
.

16
Esempio
//@ensures (*\result il massimo fra x, y, z *)
static int maxOfThree (int x, int y, int 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)
Casi limite:
x = y = z: p.es. 3, 3,3
x=y !=z
ecc.

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 *)

static void appendVector(Vector v1, Vector v2){


while (v2.size() > 0) {
v1.addElement(v2.lastElement());
v2.removeElementAt(v2.size()-1); }
}
NON vietato che v1 e v2 siano lo stesso Vector:
testando questo caso si trova un errore
18
Testing di astrazioni sui dati
Si effettua test per tutte le operazioni
del tipo di dato, MA sfruttando sinergie
tra metodi costruttori e modificatori e
metodi osservatori
Usare sistematicamente repOK
chiamandolo dopo ogni operazione di
costruzione e modifica
Caso di studio: lastrazione IntSet

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)

Dati di test funzionale per sottotipo


devono includere quelli del supertipo; in
generale sottotipo testato con
dati di test funzionali per supertipo, con in
pi chiamate del costruttore del sottotipo
dati di test funzionali aggiuntivi
caratteristici del sottotipo

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

N.B Terminologia: un test case contiene uno o pi


test

27
Esempio 2
Test di una funzione che dovrebbe
eliminare tutte le "a" da una stringa

public void testStringStripFunction() {


String expected = "bb"
StringStripper stripper = new
StringStripper();
assertEquals(expected,
stripper.stringStrip("aabaaaba"));
}

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 {

private int latoA, latoB, latoC;


public static boolean valido(int a, int b, int c)
public Triangolo(int a, int b, int c) { {
latoA = a; if (a == 0 || b == 0 || c == 0)
return false;
latoB = b;
if ((a+b < c) || (a+c < b) || (b+c < a))
latoC = c; } return false;
return true; }
public boolean valido() {
public int perimetro() {
if (latoA == 0 || latoB == 0 || if (valido())
latoC == 0) return latoA+latoB+latoC;
return false; else
if ((latoA+latoB < latoC) || return 0; }
(latoA+latoC < latoB) || }
(latoB+latoC < latoA))
return false;
return true; } 34
import junit.framework.*;
import Triangolo;

public class TestTriangolo extends TestCase {

private Triangolo t1,t2;

public TestTriangolo(String name) {


super(name); }

public void setUp() {


t1 = new Triangolo(2,4,3);
t2 = new Triangolo(2,4,8); } //NB: lati scorretti

public void testValido() {


assertTrue(t1.valido());
assertFalse(t2.valido()); }

...

35
...

public void testPerimetro() {


assertEquals(9,t1.perimetro());
usa riflessione: assume che il nome del
assertEquals(0,t2.perimetro()); } test sia il nome del metodo del TestCase
che va invocato
/*public static Test suite() {
TestSuite suite = new TestSuite();
suite.addTest(new TestTriangolo("testValido"));
suite.addTest(new TestTriangolo("testPerimetro"));
return suite; */

public static void main(String args[]) {


junit.textui.TestRunner.run(new TestTriangolo("testValido));
// junit.textui.TestRunner.run(new TestTriangolo(testPerimetro));
// junit.textui.TestRunner.run(suite()); Si pu eseguire unintera suite
// junit.swingui.TestRunner.run(.);
}
}
textui linterfaccia testuale,
swingui quella grafica

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

Molto utile un debugger: strumento per eseguire


programmi in modo controllato:
breakpoint,
esecuzione passo-passo,
visualizzazione e modifica di variabili
40
Funzionalit essenziali
Breakpoint: permettono di interrompere lesecuzione
in un certo punto
Esecuzione passo passo: permette di avanzare
lesecuzione di un passo per volta
Esame dello stato intermedio: permette di
visualizzare il valore delle singole variabili
Modifica dello stato: permette di modificare il valore
di una o pi variabili prima di riprendere lesecuzione
NB: oggi si usano debugger simbolici che
consentono di operare al livello del linguaggio di
programmazione
variabile = variabile del linguaggio, non cella di memoria
passo = istruzione del linguaggio

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

You might also like