Corso di Laurea Ingegneria Informatica Fondamenti di Informatica Dispensa 15 Correttezza A. Miola Dicembre 2011 http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Contenuti Introduzione alla correttezza Correttezza dei metodi specifica di un metodo correttezza di un metodo correttezza e responsabilità Verifica di correttezza test a scatola nera test a scatola trasparente metodi di test Test di un insieme di metodi http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Correttezza La correttezza è una delle qualità fondamentali dei prodotti software detto in modo molto preliminare e generico (come lo direbbe un uomo della strada) un prodotto software è “corretto” se consente le operazioni per cui è stato pensato Questa dispensa presenta la nozione di correttezza dei programmi, con particolare riferimento alla programmazione orientata agli oggetti, studiando gli aspetti e gli strumenti che permettono di descrivere e verificare la correttezza del software http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Esecuzione di un programma Un programma P riceve un insieme di dati in ingresso che devono soddisfare alcune condizioni, cioè hanno delle proprietà, che indichiamo con pre-condizioni Un programma P esegue le operazioni previste, in modo non ambiguo e in un tempo finito, cioè termina Un programma P fornisce un insieme di dati in uscita che devono soddisfare alcune condizioni, cioè hanno delle proprietà, che indichiamo con post-condizioni Dati in ingresso P Dati in uscita pre post http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Specifica di un programma L’espressione <pre, P, post> rappresenta la specifica del programma P La specifica di un programma P si dice soddisfatta se vale la seguente proprietà: Per ogni insieme di dati che soddisfano la pre-condizione pre, se il programma P termina quando viene eseguito su tali dati, allora i dati di uscita prodotti dal programma soddisfano la post-condizione post DEFINIZIONE - Un programma P è parzialmente corretto rispetto alla pre-condizione pre e alla post-condizione post, se la specifica <pre, P, post> è soddisfatta. http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Esempio Supponiamo di utilizzare un programma P calcolare il massimo comun divisore m di due numeri x e y interi II programma P può essere utilizzato sse i due numeri x e y sono interi e non entrambi nulli e conseguentemente la pre-condizione per P è (x e y sono interi non negativi) and ( (x > 0) or (y > 0) ) II risultato calcolato da P, m = MCD(x, y), deve soddisfare la seguente post-condizione (m divide x) and (m divide y) and (ogni numero intero che divide sia x che y divide anche m) http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Definizione Un programma P è corretto rispetto alla pre-condizione pre e alla post-condizione post se è parzialmente corretto e se esso termina ogni volta che viene eseguito su dati di ingresso che soddisfano la pre-condizione pre . . . OPPURE . . . Un programma P è corretto rispetto alla specifica <pre, P, post> se per ogni insieme di dati di ingresso che soddisfa la pre-condizione pre il programma P termina e fornisce come risultato un valore che soddisfa la post-condizione post http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Dimostrazione della correttezza . . . In base alla definizione precedente la dimostrazione della correttezza di un programma consiste dei seguenti passi: si determina la pre-condizione pre e la post-condizione post; si verifica se la specifica è soddisfatta; si stabilisce se il programma termina per ogni possibile insieme di dati di ingresso che soddisfa la pre-condizione. http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Dimostrazione della correttezza Pur essendo possibile la dimostrazione formale e rigorosa della correttezza di un programma, bisogna considerare che tali prove risultano molto complesse e lunghe Per questa ragione si segue un approccio pragmatico che viene chiamato metodo sperimentale di verifica della correttezza di un programma o test di correttezza http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Test di un programma . . . II test di un programma consiste dei seguenti passi: si determina un insieme di dati di input I; si esegue il programma con i dati I; si verificano i risultati ottenuti: 3.1 se I'esecuzione del programma non termina in un lasso di tempo ragionevole o se i risultati ottenuti dalla esecuzione non sono quelli attesi, allora il programma non è corretto 3.2 se non sono stati rilevati errori, bisogna stabilire se è necessario effettuare altre prove; in questo caso si ritorna al passo 1, altrimenti il test termina http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Test di un programma . . . Generalmente il test di un programma non permette di stabilirne la correttezza, a meno che non vengano provate tutte Ie possibili scelte di dati di ingresso – che potrebbero anche essere infinite Se eseguiamo il programma con un sottoinsieme dei possibili input non possiamo escludere che esista un insieme di dati, che non è stato provato, e che evidenzia un errore http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Test di un programma . . . Chiaramente, la prova di un programma con tutti i possibili dati di ingresso è impossibile nella maggioranza dei casi – anche se sono in numero finito Ad esempio, l'insieme dei dati di ingresso di un programma che calcola il prodotto di due interi compresi tra -100.000.000 e +100.000.000 è composto da circa 40 milioni di miliardi di elementi. Se ciascuna prova richiede un microsecondo (0.000.001 secondi) allora sono necessari 40 miliardi di secondi, circa 40.000 giorni cioè circa 110 anni http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Test di un programma Possiamo pertanto concludere che, anche se generalmente il test di un programma non permette di dimostrarne la correttezza, esso è lo strumento più usato nella pratica della programmazione Se effettuiamo molte prove e non rileviamo errori, possiamo ragionevolmente supporre che la probabilità che il programma contenga errori sia molto piccola http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Correttezza nella programmazione orientata ad oggetti In generale, è possibile ricondurre la verifica della correttezza di una classe o di una applicazione alla verifica del comportamento di un oggetto cioè dei suoi metodi facciamo delle ipotesi semplificative oggetti classe senza stato comportamento senza effetti collaterali – solo calcolo di funzioni La correttezza di un metodo è legata alla specifica del metodo – che è un particolare programma http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Alcuni termini rilevanti Malfunzionamento (o comportamento errato) di un oggetto una discrepanza tra l’effettivo comportamento esterno dell’oggetto e quello corretto Oggetto non corretto (o errato) durante il suo uso possono verificarsi malfunzionamenti un oggetto è corretto se durante il suo uso non possono mai verificarsi malfunzionamenti Errore (o difetto) è la causa di un malfunzionamento un malfunzionamento è una manifestazione di un errore http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Verifica di correttezza Le tecniche di verifica di correttezza prevedono solitamente l’uso dell’oggetto da verificare, cercando di provocare eventuali malfunzionamenti e comportamenti non corretti il verificarsi di un malfunzionamento ci garantisce la presenza di errori nell’oggetto l’assenza di malfunzionamenti certifica la correttezza dell’oggetto solo se l’oggetto è stato utilizzato in tutti i possibili modi in pratica, però, la verifica di correttezza di un oggetto viene in genere basata su un numero limitato di prove La verifica di correttezza di un oggetto può semplicemente confermare la presenza di errori, e mai la loro assenza http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Individuazione e correzioni degli errori Individuazione degli errori attività di ricerca degli errori la verifica di correttezza non dice quanti e quali sono tipologie di errori errori di uso del linguaggio di programmazione errori di codifica errori nella realizzazione dell’algoritmo errori nella scelta dell’algoritmo è problematico solo trovare gli errori non riconosciuti dal compilatore la correzione di un errore è tanto più costosa quanto più tardi l’errore viene rilevato e corretto rispetto al momento in cui è stato introdotto http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Specifica di un metodo La nozione di specifica di un metodo formalizza l’idea di comportamento per cui il metodo è stato pensato La specifica di un metodo corrisponde alla specifica del problema che il metodo deve risolvere La specifica di un metodo è parte della sua documentazione http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Esempi . . . Un metodo per calcolare il massimo comun divisore di due numeri interi positivi N e M pre-condizione - N>0 && M>0 post-condizione - il valore restituito dal metodo è il massimo comun divisore di N e M /* Calcola il massimo comun divisore di n e m. * Il masssimo comun divisore di n e m è un numero * intero d che è divisore sia di n che di m * e tale che ogni altro divisore intero comune * a n e m è minore di d. */ public static int mcd(int n, int m) { // pre: n>0 && m>0 ... } http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Esempi Un metodo per calcolare il numero di occorrenze di un carattere in una stringa non nulla /* Calcola il numero di occorrenze del * carattere car nella stringa s, * ovvero il numero di caratteri di s * che sono uguali a car. */ public static int occorrenze(String s, char car) { // pre: s!=null ... } http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Non correttezza di un metodo Un metodo è non corretto se esiste almeno un insieme di dati di ingresso che soddisfa la pre-condizione pre per cui l’esecuzione del metodo non termina oppure se esiste almeno un insieme di dati di ingresso che soddisfa la pre-condizione pre per cui l’esecuzione del metodo termina in modo anormale (con una eccezione) oppure se esiste almeno un insieme di dati di ingresso che soddisfa la pre-condizione pre per cui l’esecuzione del metodo termina ma il valore restituito non soddisfa la post-condizione post La verifica di correttezza di un metodo consiste proprio nel cercare contro-esempi relativi alla sua correttezza http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Correttezza e responsabilità Le responsabilità circa la correttezza di un metodo vengono così ripartite chi usa un metodo deve invocarlo fornendogli in ingresso dei dati che soddisfano la pre-condizione chi realizza un metodo deve far sì che il metodo termini e produca un risultato corretto ogni volta che la pre-condizione è verificata chi realizza un metodo può assumere che la pre-condizione sia verificata (solitamente) non deve verificare se i dati di ingresso soddisfano la pre-condizione non ha responsabilità se il metodo produce un errore su dati che non soddisfano la pre-condizione http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Correttezza e responsabilità . . . /* Calcola mediante somme ripetute il prodotto n*m * dei numeri interi n e m. */ public static int prodottoInteri(int n, int m) { // pre: true (nessuna condizione su n e m) int p; // il prodotto di n e m int i; // per iterare tra 1 e n p = 0; for (i=1; i<=n; i++) p = p+m; return p; } . . . nperm = prodottoInteri(-2, 3); // 0 anziché -6 è da considerarsi errato il metodo prodottoInteri - la responsabilità è di chi ha realizzato il metodo http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Correttezza e responsabilità /* Calcola mediante somme ripetute il prodotto n*m * dei numeri naturali n e m. */ public static int prodottoNaturali(int n, int m) { // pre: n>=0 && m>=0 int p; // il prodotto di n e m int i; // per iterare tra 1 e n p = 0; for (i=1; i<=n; i++) p = p+m; return p; } . . . nperm = prodottoNaturali(-2, 3); // 0 anziché -6 è da considerarsi errato il metodo che invoca prodottoNaturali - la responsabilità è di chi usa il metodo http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Scelta dei dati di ingresso per un test Con quali dati effettuare il test di un metodo? ci sono varie tecniche di verifica, che si differenziano sulla scelta dell’insieme dei dati di test su cui basare la verifica Due approcci principali test a scatola nera l’insieme dei dati di ingresso viene scelto solo in riferimento alla specifica del metodo, senza far riferimento ai dettagli realizzativi del metodo test a scatola trasparente l’insieme dei dati di ingresso viene scelto anche in riferimento ai dettagli realizzativi del metodo http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Evitare scelte casuali dei dati di test È importante premettere che il test di un programma non consiste semplicemente nell'eseguire il programma una o più volte con dati scelti in modo casuale, ma è un'attività che deve essere condotta e pianificata durante tutta la fase di progettazione L'esempio seguente mostra come una scelta casuale dei dati di prova non risulta adeguata http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Esempio . . . Siano date due stringhe a e b, di al più 20 caratteri, di lunghezza pari a lunA e lunB rispettivamente Si consideri il seguente frammento di programma che dovrebbe assegnare ad una variabile booleana uguali il valore vero se Ie due stringhe sono uguali, i valore falso altrimenti . . . if lunA == lunB for (i=0; i < lunA; i++) uguali = a.charAt(i) == b.charAt(i) else uguali = false; http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Esempio . . . La soluzione precedente è chiaramente errata perché è sufficiente che due parole della stessa Iunghezza abbiano I'ultimo carattere uguale affinché la variabile uguali sia posta a vero Pertanto il frammento di programma con le stringhe 'bianco' e 'giallo' come dati di ingresso assegnerà alla variabile uguali il valore vero Si noti che una esecuzione del programma con una scelta casuale dei dati rende molto improbabile la scoperta dell'errore http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Esempio . . . Infatti l'errore può esser rilevato solo se i dati di prova soddisfano contemporaneamente Ie due seguenti condizioni: - lunA pari a lunB - a.charAt(lunA-1) pari a b.charAt(lunB-1) Con una scelta completamente casuale dei dati la prima condizione è vera con probabilità pari a 1/20 = 0.05 (Ie stringhe hanno lunghezze comprese fra 1 e 20), mentre la seconda è vera con probabilità pari a circa 1/26 = 0.038 (i caratteri possibili sono 26) http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Esempio Pertanto una scelta casuale dei dati di prova ha una probabilità pari a (0.05 x 0.038)=0.0019 di evidenziare I'errore Se si eseguono più prove allora la probabilità di trovare I'errore aumenta ma molto lentamente, infatti, la probabilità di trovare I'errore eseguendo 100 prove casuali in modo indipendente è inferiore a 0.2 http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Test a scatola nera Il test a scatola nera di un metodo è basato sulla specifica del metodo l’insieme dei possibili dati di ingresso per il metodo viene partizionato in sottoinsiemi (insiemi di equivalenza) il metodo viene eseguito utilizzando una sola combinazione dei valori per ciascun insieme di equivalenza L’approccio di test a scatola nera è basato sulla seguente ipotesi se il metodo si comporta correttamente per una combinazione dei dati di ingresso X scelta nell’insieme di equivalenza CX, allora il metodo si comporta correttamente anche su ogni altra combinazione dei dati di ingresso scelta nell’insieme CX è solo una ipotesi e potrebbe quindi essere sbagliata http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Scelta degli insiemi di equivalenza . . . Idea alcuni insiemi di equivalenza rappresentano dati “normali” per il metodo altri insiemi di equivalenza rappresentano dati “particolari” per il metodo casi o valori al limite http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Scelta degli insiemi di equivalenza Leggi dalla tastiera una sequenza di numeri interi e calcolane la somma scelta degli insiemi di equivalenza la sequenza è vuota – caso particolare la sequenza contiene un solo elemento – caso particolare la sequenza contiene più elementi – caso normale Il test viene poi svolto effettuando una prova per ciascun insieme di equivalenza scegliendo un insieme di dati rappresentativo per ciascun insieme di equivalenza http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Scelta dei dati Leggi dalla tastiera una sequenza di numeri e calcolane la somma scelta dei dati per ciascun insieme di equivalenza la sequenza è vuota – la sequenza contiene un solo elemento – 8 la sequenza contiene più elementi – 1 2 3 4 5 che cosa può accadere se la sequenza vale 1 2 3 4 5 il risultato è 15 – OK il risultato è 14 o 10 – errore di uno il risultato è 0 – gli elementi non sono stati sommati correttamente il programma rimane in attesa di ulteriori dati in ingresso sequenze meno significative la sequenza vale 0 0 0 0 0 la sequenza vale 1 1 1 1 1 http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Test a scatola trasparente I test a scatola trasparente di un metodo sono basati anche sulla struttura del metodo l’idea alla base di questo approccio è che un test può considerarsi concluso solo quando ciascuna istruzione del metodo sia stata eseguita almeno una volta questo richiede, ad esempio, di scegliere i dati di ingresso in modo tale che ciascuna istruzione sia eseguita almeno una volta, ed inoltre che ciascuna condizione sia verificata almeno una volta e non verificata almeno una volta http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Metodi di test Un modo comune per effettuare, in pratica, il test di un metodo è definire un metodo di test per il metodo sono stati scelti gli insiemi di equivalenza per il test e i relativi dati rappresentativi un metodo di test per il metodo contiene una invocazione del metodo per ciascuno dei dati scelti i parametri attuali dell’invocazione del metodo sono letterali che rappresentano i dati scelti il valore restituito dal metodo viene confrontato con quello atteso e/o visualizzato sullo schermo un commento motiva la scelta dell’insieme di equivalenza e dei dati di prova http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Metodo di test per il massimo comun divisore /* Metodo di test per int mcd(int n, int m). * Questo metodo deve essere invocato * da una applicazione di test */ public static void testMcd() { /* n=1, m>1 */ System.out.println("mcd(1,3): [1]: " + Mcd.mcd(1,3)); /* n>1, m multiplo di n */ System.out.println("mcd(4,12): [4]: " + Mcd.mcd(4,12)); /* n>1, m>1, n e m hanno un multiplo comune */ System.out.println("mcd(8,12): [4]: " + Mcd.mcd(8,12)); ... } http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Documentazione e ripetibilità dei test Se si procede come descritto il metodo di test costituisce una documentazione della verifica di correttezza effettuata il test è riproducibile e ripetibile se il metodo non passa il test, per la nuova versione del metodo (in cui dovrebbero essere stati individuati e corretti alcuni errori) può essere usato lo stesso metodo di test il test può essere ripetuto quando si definisce una nuova versione del metodo (ad esempio, più efficiente) In generale, non è una buona idea eseguire un test sulla base di dati letti dalla tastiera il test non è documentato e non è ripetibile ma – ovviamente - è l’unica scelta possibile nella verifica di applicazioni interattive http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Test di un insieme di metodi . . . Il test di un singolo metodo è solo una componente della verifica e correzione di un oggetto o di un intero programma in genere bisogna verificare un gruppo composto da molti metodi, che interagiscono in modo anche complesso Ad esempio - dato un numero naturale n, conta quanti sono i numeri primi minori o uguali a n il metodo (di supporto) boolean primo(int n) verifica se n è un numero primo il metodo (principale) int contaPrimi(int n) risolve l’intero problema La strategia più redditizia porta ad implementare e verificare un metodo alla volta – ma in che ordine? http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
. . . Test di un insieme di metodi metodo m invoca metodo m1 metodo m2 metodo m11 http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Strategie per il test di un insieme di metodi Sono possibili due strategie implementare e verificare prima il metodo primo e poi il metodo contaPrimi implementare e verificare prima il metodo contaPrimi e poi il metodo primo Le due strategie si differenziano come segue nella prima strategia (bottom up) vengono prima realizzati e verificati i metodi invocati, e poi quelli che invocano metodi già verificati nella seconda strategia (top down) vengono prima realizzati e verificati i metodi che invocano altri metodi, e poi quelli invocati di solito viene usata la strategia bottom up http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza
Riferimenti al libro di testo Per lo studio di questi argomenti si fa riferimento al libro di testo, e in particolare al capitolo 16 http://www.dia.uniroma3.it/~java/fondinf1/ Correttezza