Il Testing White Box è un testing strutturale, poichè utilizza la struttura interna del programma per ricavare i dati di test.
Tramite il testing White Box si possono formulare criteri di copertura più precisi di quelli formulabili con testing Black Box.
Test White Box che hanno successo possono fornire maggiori indicazioni al debugger sulla posizione dell’errore.
Il grafo del flusso di controllo (Control-Flow Graph) di un programma P: CFG (P) = <N, AC,nI, nF>
dove:
<N, AC> è un grafo diretto con archi etichettati,
{nI, nF} ⊆ N, N- {nI, nF} = Ns ∪ Np
Ns e Np sono insiemi disgiunti di nodi istruzione e nodi predicato;
AC ⊆ N-{nF}x N-{nI } x {vero, falso, incond} rappresenta la relazione flusso di controllo; nI ed nF sono detti rispettivamente nodo iniziale e nodo finale.
Un nodo n∈ Ns ∪ {nI} ha un solo successore immediato e il suo arco uscente è etichettato con incond.
Un nodo n∈Np ha due successori immediati e i suoi archi uscenti sono etichettati rispettivamente con vero e falso.
Il nodo iniziale è l’unico senza archi entranti.
Il nodo finale è l’unico senza archi uscenti.
Un nodo istruzione ha un solo arco uscente.
Un nodo predicato ha due archi uscenti, etichettati dai valori true e false.
Un nodo istruzione o predicato deve avere almeno un arco entrante.
Non c’è limite superiore al numero di archi entranti in un nodo istruzione o predicato.
I nodi 2, 4, 6, 8 potevano anche essere omessi, dato che ad essi non corrisponde alcuna istruzione esecutiva, nel codice eseguibile del programma. Uno switch può essere risolto trasformandolo (come fa il compilatore) in una serie di if else in cascata.
Il CFG a destra esprime più precisamente la semantica del ciclo for, così come viene trasformato in codice oggetto da un compilatore C. Infatti ad ogni ciclo for corrisponde un init preventivo, una decisione all'inizio di ogni ciclo (come in un while) e un codice di continuazione cont (ad esempio i++) da ripetere prima di ricominciare il ciclo.
Per semplicità, il CFG è stato studiato rispetto ad un linguaggio di programmazione di alto livello (in questo caso C).
In realtà, il CFG dovrebbe modellare l’effettiva esecuzione di un programma, quindi il suo codice macchina.
Nella trasformazione di un programma nel suo CFG corrispondente bisognerebbe sempre considerare il risultato della sua compilazione (come nel caso del for).
Una decisione corrisponde ad una espressione booleana oggetto di un costrutto.
Ad una decisione corrisponde un nodo predicato con due uscite, corrispondenti ai valori vero e falso.
Volendo scendere più nel dettaglio, una decisione può essere scritta come una combinazione di condizioni booleane. Ad esempio:
(a>0) and (b<0) or (c>d)
È una decisione composta di tre condizioni.
In linguaggio macchina, esistono istruzioni che elaborano singole condizioni piuttosto che complesse decisioni.
Per massimizzare la fedeltà del CFG al programma che verrà eseguito, bisognerebbe considerare un nodo predicato per ogni condizione.
Problema: non sempre è possibile conoscere in che ordine le condizioni vengano considerate dal compilatore.
int Quadrato (int x, int y){
1. read(x);
2. if (x > 0){
3. n = 1;
4. y = 1;
5. while (x > 1) {
6. n = n + 2;
7. y = y + n;
8. x = x - 1;
}
9. write(y);
}
}
Possono essere raggruppate quelle istruzioni che verranno sempre eseguite in sequenza.
Inoltre non deve esserci alcuna freccia che porta ad una istruzione diversa dalla prima di quelle raggruppate.
Ad esempio, 5 non può essere raggruppata con 3 e 4.
Copertura dei comandi (statement test)
Es. nella Procedura quadrato, la test suite TS={x=2} garantisce la copertura di tutti i nodi, ma non dell’arco (2, Fine).
Copertura delle decisioni (branch test).
Richiede che ciascun arco del CFG sia attraversato almeno una volta;
In questo caso ogni decisione è stata sia vera che falsa in almeno un test case.
Nella procedura Quadrato, la TS={x=2, x=-1} garantisce la copertura delle decisioni.
Un limite è legato alle decisioni in cui più condizioni (legate da operatori logici AND ed OR) sono valutate.
Ciascuna condizione nei nodi decisione di un CFG deve essere valutata almeno una volta sia per valori true che false.
Esempio:
int check (x);// controlla se un intero è fra 0 e 100
int x;
{ if ((x>=0) && (x<= 200))
check= true;
else check = false;
}
TS1={x=5, x=-5 } valuta la decisione sia per valori True che False, ma non le condizioni (la seconda condizione è sempre true).
TS2={x= -3, x=210} è una Test suite che copre tutte le condizioni ma non tutte le decisioni (la and è in entrambi i casi complessivamente false).
Per garantire contemporaneamente sia la copertura di tutte le condizioni che di tutte le decisioni, bisogna cercare di coprire tutte le combinazioni delle condizioni.
Es. If (x>0 && y>0) …
TS1={(x=-1, y=-1), (x= 2, y=3), (x=5, y=-4), (x=-3, y=4)} copre tutte le combinazioni delle condizioni.
Problema:
Nella condizione dell’esempio precedente (x>=0 && x<=200) non tutte le combinazioni di condizioni sono verificabili (la combinazione false, false non è ottenibile).
Un cammino è un’esecuzione del modulo dal nodo iniziale del Cfg al nodo finale.
Un cammino si dice indipendente (rispetto ad un insieme di cammini) se introduce almeno un nuovo insieme di istruzioni o una nuova condizione in un CFG un cammino è indipendente se attraversa almeno un arco non ancora percorso.
L’insieme di tutti i cammini linearmente indipendenti di un programma forma i cammini di base; tutti gli altri cammini sono generati da una combinazione lineare di quelli di base.
Dato un programma, l’insieme dei cammini di base non è unico.
Il numero dei cammini linearmente indipendenti di un programma è pari al numero ciclomatico di McCabe:
Test case esercitanti i cammini di base garantiscono l’esecuzione di ciascuna istruzione almeno una volta e anche il percorrimento di ogni arco.
Copertura dei cammini (path test)
Copertura dei cammini indipendenti
La copertura delle decisioni implica la copertura dei nodi.
La copertura delle condizioni non sempre implica la copertura delle decisioni e la copertura dei nodi.
La copertura dei cammini linearmente indipendenti implica la copertura dei nodi e la copertura delle decisioni.
La copertura dei cammini è un test ideale ed implica tutti gli altri.
È possibile riconoscere automaticame.nte quale cammino linearmente indipendente viene coperto dall’esecuzione di un dato test case
È indecidibile il problema di trovare un test case che va a coprire un dato cammino;
Alcuni cammini possono risultare non percorribili (infeasible).
La copertura dei cammini linearmente indipendenti non garantisce da errori dovuti, ad esempio, al numero di cicli eseguiti, per i quali sarebbe necessaria la copertura di tutti I cammini, che però rappresenta il testing esaustivo!
1. #include
2. using namespace std;
3. int main ()
4. {
5. char risposta;
6. cout<<"\n Vuoi trovare il massimo fra tre interi? (Inserisci N o n per finire)"; cin>> risposta;
7. while(risposta!='N' && risposta!='n')
8. {
9. int num1, num2, num3;
10. cout<<"\n Inserisci primo numero: "; cin>> num1;
11. cout<<"\n Inserisci secondo numero: "; cin>> num2;
12. cout<<"\n Inserisci terzo numero: "; cin>> num3;
13. int max = num1;
14. if ( num2 > max )
15. max = num2;
16. if ( num3 > max )
17. max = num3;
18. cout<<"\n Il massimo e':"<<<"\n";
19. cout<<"\n Vuoi continuare? (Inserisci N o n per finire)"; cin>> risposta;
20. }
21. system("Pause");
22. return(0);
23. }
a) Disegnare il CFG della funzione e calcolarne la complessità ciclomatica;
Progettare un insieme di casi di test (ognuno costituito da un insieme di valori per le variabili in input) in grado di coprire:
b) tutti le istruzioni del programma;
c) tutte le decisioni del programma;
d) tutte le condizioni del programma;
e) tutte le condizioni e decisioni;
f) tutti i cammini linearmente indipendenti.
1. Introduzione
4. Casi d'uso
6. Class Diagram – parte prima
7. Class diagram – parte seconda
8. Class diagram – parte terza
9. Modellazione architetturale
10. Sequence Diagram
14. Progettazione Architetturale
15. Design Patterns – Parte prima
16. Design Patterns – Parte seconda
17. Progettazione dell'interfaccia utente
Sommerville, Ingegneria del Software, 8° edizione, Capitoli 22-23-24.