CAPITOLO 1: SOFTWARE MAINTENANCE E LEGACY SYSTEM

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "CAPITOLO 1: SOFTWARE MAINTENANCE E LEGACY SYSTEM"

Transcript

1 IS2 -> Capitolo 1 CAPITOLO 1: SOFTWARE MAINTENANCE E LEGACY SYSTEM MANUTENZIONE DEL SOFTWARE La predizione della manutenzione consiste nel verificare quali componenti possono causare problemi e a quali costi si andrebbe incontro. Il costo della manutenzione dipende dalla qualità del componente. Una volta che il software è messo in opera, emergono nuovi requisiti e si modificano quelli esistenti. La maggior parte dei budget software nelle grandi compagnie è dunque investita per mantenere i sistemi esistenti, e non ci deve sorprendere di dati come quelli di Erlinkh: sostiene che il 90% dei costi del software sono costi di evoluzione, ma questa percentuale è molto incerta, poiché le persone indicano cose diverse quando si riferiscono ai costi di evoluzione o manutenzione. Quando la transizione dallo sviluppo all evoluzione non è trasparente, il processo di modifica del software dopo la sua consegna è spesso chiamato manutenzione del software. Dinamiche di evoluzione di un programma In seguito ad alcuni studi, sono state proposte un insieme di leggi (le leggi di Lehman) che riguarda le modifiche ai sistemi, affermando che queste leggi (in realtà ipotesi) sono invarianti e applicabili diffusamente. N Legge Descrizione 1 Modifiche continue 2 Complessità crescente 3 Evoluzione dei grandi programmi 4 Stabilità organizzativa 5 Conservazione della familiarità Un programma utilizzato in un ambiente reale deve per forza cambiare o diventerà progressivamente meno utile. Quando un programma in evoluzione cambia, la sua struttura tende a diventare più complessa. Si devono dedicare risorse aggiuntive per preservare e semplificare la struttura. L evoluzione dei programmi è un processo autoregolato: gli attributi di sistema come la dimensione, il tempo tra una release e l altra e il numero di errori rilevati sono approssimativamente invarianti per ciascuna release del sistema. Durante il ciclo di vita di un programma il suo tasso di sviluppo è approssimativamente costante e indipendente dalle risorse dedicate allo sviluppo del sistema stesso Durante il ciclo di un sistema le modifiche incrementali per ogni release sono approssimativamente costanti. 6 Crescita continua Le funzionalità offerte dai sistemi devono crescere continuamente per mantenere soddisfatti gli utenti. 7 Qualità deteriorata La qualità dei sistemi apparirà deteriorata se non vengono adattati ai cambiamenti del loro ambiente operativo. 1

2 IS2 -> Capitolo 1 8 Sistema feedback I processi evolutivi incorporano sistemi feedback multi-agente e multi-ciclo, e occorre trattarli come sistemi feedback per raggiungere significativi miglioramenti nei prodotti. Manutenzione del software La manutenzione del software è il processo generale di modifica di un sistema dopo la consegna. I cambiamenti sono implementati modificando i componenti esistenti del sistema e, dove necessario, aggiungendone nuovi. Ci sono 3 diversi tipi di manutenzione del software: Manutenzione per riparare errori nel software: gli errori di programmazione in genere sono relativamente economici da correggere, mentre quelli di progettazione sono più costosi perché possono richiedere la riscrittura di diversi componenti del programma; gli errori nei requisiti sono i più costosi da riparare a causa dell estensiva riprogettazione del sistema. Manutenzione per adattare il software a un diverso ambiente operativo: questo tipo di manutenzione è richiesto quando alcuni aspetti dell ambiente del sistema, come l hardware, il SO o altro software di supporto, cambiano; il sistema applicativo deve essere modificato perché sia adeguato ad affrontare questi cambiamenti dell ambiente. Manutenzione per aggiungere o modificare le funzionalità del sistema: questo tipo di manutenzione è necessaria quando cambiano i requisiti di sistema in risposta a cambiamenti organizzativi o di business. Aggiungere nuove funzionalità dopo la consegna è costoso, perché comprendere il sistema e analizzare l impatto delle modifiche proposte richiede tempo. Il costo per trasformare un sistema legacy in una versione strutturata è paragonabile alla creazione di un nuovo software. Più si vuole rendere un sistema distribuito, più aumentano i costi. I costi di manutenzione in proporzione ai costi di sviluppo variano da un dominio di applicazione all altro. Per i sistemi integrati real-time i costi di manutenzione possono essere fino a 4 volte più alti dei costi di sviluppo: i requisiti di alta affidabilità e prestazione di questi sistemi spesso richiedono moduli strettamente collegati e quindi difficili da modificare. Un importante ragione per cui i costi di manutenzione sono alti è che aggiungere funzionalità quando un sistema è operativo è più costoso che implementare la stessa funzionalità durante lo sviluppo. Esistono alcuni fattori chiave che distinguono lo sviluppo dalla manutenzione aumentandone i costi: Stabilità del team: dopo la consegna è normale sciogliere il team. I nuovi componenti del team per la manutenzione possono non comprendere il sistema. Buona parte dello sforzo durante il processo di manutenzione è impiegato nella comprensione del sistema esistente prima di implementarvi le modifiche. 2

3 IS2 -> Capitolo 1 Responsabilità contrattuale: il contratto per la manutenzione del sistema di solito è separato dal contratto di sviluppo, perché può essere stipulato anche da altre compagnie. Questo fattore determina l assenza di incentivi a scrivere il software in modo che sia facile da modificare prendendo anche delle scorciatoie che fanno aumentare i costi di manutenzione. Capacità dello staff: lo staff di manutenzione è spesso inesperto e ha poca familiarità con il dominio di applicazione; viene assegnato di solito ai giovani perché ritenuto un processo meno impegnativo e dato che il sistema può essere scritto con linguaggi di programmazione obsoleto, questo può in alcuni casi prima essere compreso dal team. Età e struttura del programma: dato che i programmi invecchiano e la loro struttura tende a deteriorarsi, spesso si perde tempo cercando le versioni giuste dei componenti di sistema da modificare, soprattutto se la progettazione del sistema non è stata fatta tramite le moderne tecniche di ingegneria del software. I primi tre problemi derivano dal fatto che molte organizzazioni considerano lo sviluppo e la manutenzione attività separate: dove la manutenzione è vista come un attività secondaria e non c è incentivo a spendere soldi durante lo sviluppo per ridurre i costi delle modifiche del sistema. L unica soluzione a questo problema è considerare i software nel futuro come software in continuo sviluppo. Il quarto problema può essere risolto applicando tecniche di re-ingegnerizzazione del software in modo da migliorare la struttura e la complessità del sistema. Il processo di manutenzione I processi evolutivi comprendono le attività fondamentali di analisi dei cambiamenti, pianificazione delle release, implementazione del sistema e rilascio del sistema ai clienti. Il costo e l impatto di tali modifiche sono valutati per vedere quanta parte del sistema è influenzata dalla modifica e quanto può costare implementarla. Se le modifiche proposte sono accettate, viene pianificata una nuova release del sistema; durante la pianificazione vengono considerate tutte le modifiche proposte (riparazione degli errori, adattamenti e nuove funzionalità) e quindi si decide quali modifiche implementare nella successiva versione del sistema. Nella richiesta di cambiamento i nuovi requisiti che rispecchiano le modifiche vengono proposti, analizzati e convalidati; i componenti sono riprogettati e implementati e il sistema è nuovamente testato; se necessario si può eseguire la prototipizzazione delle modifiche proposte come parte del processo di analisi delle modifiche. Se ci sono cambiamenti imprevisti nell azienda che sta utilizzando il sistema, come la comparsa di nuovi concorrenti o l introduzione di una nuova legislazione, se avviene un grave errore di sistema, se le modifiche hanno effetti inattesi, la necessità di effettuare velocemente il cambiamento comporta l impossibilità di seguire il processo formale di analisi delle modifiche: invece di modificare i requisiti e il progetto, si effettua una correzione di emergenza (emergency fix) al programma per risolvere il problema 3

4 IS2 -> Capitolo 1 immediato; il problema però è che i requisiti, la progettazione del software e il codice diventino gradualmente inconsistenti. Prevedere la manutenzione Si dovrebbero prevedere i probabili cambiamenti del sistema e le parti presumibilmente più difficili da mantenere. Se una modifica al sistema deve essere accettata o meno dipende dalla manutenibilità dei componenti del sistema influenzati da tale modifica. Implementare le modifiche al sistema tende a deteriorarne la struttura e quindi a ridurre la sua manutenibilità. I costi di manutenzione dipendono dal numero di modifiche e il costo dell implementazione di una modifica dipende dalla manutenibilità dei componenti del sistema. Per valutare le relazioni tra un sistema e il suo ambiente si dovrebbero determinare alcuni elementi: Il numero e la complessità delle interfacce del sistema: più grande è il numero di interfacce e più sono complesse, più è probabile che sia necessario modificarle. Il numero dei requisiti di sistema intrinsecamente volatili: i requisiti che riflettono le politiche e le procedure organizzative sono probabilmente più volatili dei requisiti che si basano su stabili caratteristiche del dominio. I processi aziendali in cui il sistema viene utilizzato: l evoluzione dei processi aziendali genera richiesta di modifica del sistema; più processi aziendali usano un dato sistema, più saranno le relative domande di modifica. Ecco alcuni esempi di misurazioni che possono essere utilizzate per valutare la manutenibilità: Il numero di richieste per manutenzione correttiva Tempo medio richiesto per l analisi dell impatto Tempo medio richiesto per implementare una richiesta di modifica Numero di richieste di modifica in attesa Evoluzioni architetturali C è la necessità di convertire molti sistemi legacy da un architettura centralizzata verso un architettura client server. Bisogna valutare i seguenti cambiamenti: I costi hardware. I server sono sostituiti da mainframe Le interfacce utente (gli utenti si aspettano interfacce grafiche) Accesso al sistema distribuito senza tener conto delle posizioni geografiche, ecc (trasparenza). I fattori di un architettura distribuita sono: 4

5 IS2 -> Capitolo 1 1. Importanze di business: valutano il ritorno dell investimento e da quanto resterà importante. Se distribuire un sistema consente un più efficiente supporto ai processi, allora questa può essere un efficace strategia di evoluzione del sistema. 2. Età del sistema: più vecchio è il sistema, più sarà difficile intervenire sul sistema a causa delle elevate manutenzioni che sono state effettuate. 3. Struttura del sistema: bisogna cambiare l architettura per rendere il sistema più semplice. 4. Politiche per ottenere l hardware: le applicazioni distribuite devono essere necessarie se vi sono politiche d azienda per sostituire i server coi costosi mainframe. A volte si ha la necessità di rendere un sistema legacy distribuito. Per far ciò è necessario introdurre un middleware che ne predispone le tecniche necessarie per effettuare le operazioni. Ci sono una serie di fattori che fanno variare i costi: o Minore è la distribuzione dal server verso i client, maggiori saranno i costi per l evoluzione architetturale. o Il semplice modello distribuito è l User Interface UI dove l interfaccia di ogni utente è implementata sul client. o La difficoltà maggiore si ha quando il server deve provvedere alla gestione dei dati e il servizio è implementato sui client. Quando si parla di User Interface Distribution, un UI ha necessità di molte risorse per implementare una interfaccia utente grafica. Quando vi è una chiara separazione tra l UI e l applicazione il sistema legacy può essere modificato per realizzare una UI distribuita. Altrimenti, il middleware che gestisce la grafica può tradurre una interfaccia testuale in un interaccia grafica. LEGACY SYSTEM Il software evolve, a volte anche per periodi molto lunghi. I legacy system sono sistemi software vecchi, mantenuti da persone diverse dai creatori in quanto è sempre meglio vecchio ma che funziona. I sistemi legacy sono molto spesso software aziendali che gestiscono i vari processi (magazzino, etc.). A volte è possibile sostituire questi software con sistemi integrati (SAP) se gestivano in modo generico tutti gli 5

6 IS2 -> Capitolo 1 aspetti dell'azienda. Questi software nuovi sono comunque molto costosi. In altri campi, come le transazioni bancarie, è impensabile sostituire i software legacy. La grande azienda si può permettere la sostituzione di sistema legacy con SAP, adattando i suoi processi aziendali al nuovo software, risolvendo una volta e per sempre i problemi che derivano dall'utilizzo di software vecchio. Nel caso in cui invece non esiste un sistema software sul mercato che faccia le stesse cose del sistema legacy, non c'è garanzia che il nuovo software funzioni altrettanto bene, non c'è garanzia che l'investimento rientri. Quindi molte aziende preferiscono mantenere il vecchio software. È rischioso scrivere un nuovo software per sostituire il vecchio in quanto del vecchio raramente esiste documentazione e quindi è difficile tirare fuori i requisiti e altre informazioni utili. Le regole di business funzionano bene con questi sistemi, sono codificate nel sistema e quindi non esiste documentazione neanche di questi. C'è anche da dire che la manutenzione del vecchio software costa, e quindi si deve paragonare il costo del nuovo software con la manutenzione del vecchio. È costoso in quanto è difficile trovare programmatori che abbiano conoscenze di vecchi sistemi di programmazione. Modifiche non documentate ed appezzottate. Manutenzione o sostituzione? Questo è il dilemma! Strutture sistemi legacy Esistono tecniche per estendere i vecchi software. I sistemi legacy devono essere considerati insieme all'architettura su cui girano e non indipendentemente da questa, includendo i sistemi operativi, i software applicativi, file system, processi, regole e politiche di business. I cambiamenti di una componente del sistema (hardware, software, so, etc.) sono impossibili in quanto esistono differenze enormi tra le varie architetture. Se il sistema è ben strutturato e diviso si può pensare di sostituire in modo incrementale le varie componenti, riducendo così i rischi che comporta una migrazione completa. Non è banale la migrazione dei dati da file a database, in quanto un software può gestire un record come gli è più comodo e la trasformazione da file a tabella diventa difficile da realizzare. Una volta fatta la migrazione, il software originale continuerà ad usare la sua struttura dei file e quindi avrà bisogno di un middleware che farà la conversione tra quello che vuole il programma e il database relazionale. I sistemi legacy non sono object oriented, ma function oriented, con funzioni chiamate da altre funzioni, che possono avere dati locali o dati condivisi con altre procedure. La progettazione di questi sistemi consta di 3 fasi: progettazione del flusso di dati, che modella il processing dei dati attraverso diagrammi a flusso di dati; decomposizione strutturale, che modella come le funzioni sono decomposte in sottofunzioni attraverso rappresentazioni grafiche della struttura; progettazione dettagliata, che descrive in dettaglio le entità e le interfacce ricavate. Il modello caratteristico dei sistemi function oriented è il modello inputprocess-output: le componenti di input leggono e validano l'input da terminale o da file, le componenti di processing eseguono alcune trasformazioni sui dati, le componenti di output formattano e stampano il risultato della computazione. Questo modello è facilmente rappresentato dai diagrammi a flusso di dati, che possono essere facilmente trasformati sia in una progettazione sequenziale (per cui le componenti di processing del diagramma diventano procedure del sistema), sia in una parallela (per cui le componenti 6

7 IS2 -> Capitolo 1 sono eseguite da task o processi differenti). Ci sono 2 tipi di business data processing system che possono anche condividere alcune informazioni tra loro: batch processing system (input e output da file: ad esempio la creazione dell'estratto conto) e i transaction processing (input da terminale utente output su database: ad esempio la gestione di una operazione di prelevamento). I transaction processing, per la loro caratteristica stateless, ossia per il fatto di dipendere da un singolo input e dal non aver bisogno di mantenere la storia degli input ricevuti, sono naturalmente implementati attraverso un approccio funzionale, che risulta il migliore approccio,per cui sembrerebbe inutile, costoso e rischioso migrarli ad un approccio object-oriented I sistemi legacy sono stati sviluppati utilizzando un approccio function-design. Nella programmazione ad oggetti la progettazione dei dati e del software è integrata, mentre nell'approccio a funzioni le due progettazioni erano distinte. Il processo veniva visto come dei dati che entravano, venivano elaborati, trasformati da varie funzioni che poi li restituivano in output per l'input ad altre funzioni (data-flow). Il passaggio da data-flow alla struttura a moduli è chiamata functional design process. La progettazione di un software procedurale si fa in PDL. Una volta che si è deciso di mantenere il software legacy bisogna decidere come far evolvere o manutenere tale software. Le possibili categorie dei sistema legacy sono: Bassa qualità, basso valore di business: questo sistema deve essere gettato via; Bassa qualità, alto valore di business: questo contribuisce in modo molto importante al business ma è molto costoso da mantenere. Dovrebbe essere re-ingegnerizzato o rimpiazzato se un sistema appropriato è disponibile Alta qualità, basso valore di business: il sistema deve essere rimpiazzato con COTS (Commercial Of- The-Shelf), eliminato completamente oppure mantenuto Alta qualità, alto valore di business: il sistema deve continuare ad operare usando un normale sistema di manutenzione. La valutazione del valore di business deve tenere conto di vari aspetti e dei punti di vista di tutti i soggetti coinvolti nel processo di business. La valutazione della qualità deve valutare il processo di business, l'ambiente, le applicazioni quindi verificare se il processo di business è ben supportato dal software legacy, etc. Verificare che tutti i componenti, sia hardware sia software, siano ancora disponibili e manutenibili (esistenza dei fornitori, età, tasso di fallimento, prestazioni, interoperabilità, costi di manutenzione). Per le applicazioni bisogna verificare la comprensibilità, documentazione, prestazioni, linguaggio di programmazione utilizzato, consistenza dei dati, gestione delle configurazioni, etc. 7

8 IS2 -> Capitolo 1 IEEE STANDARD 1219 Fasi dei processi di manutenzione Il processo di manutenzione fa si che il software evolve, innescato da richieste di modifica. Nel momento che ho una baseline, ho bisogno che vi sia una gestione informale della richiesta la quale poi viene assegnata. Successivamente entra in gioco la change request, la quale viene analizzata e si da inizio al processo di manutenzione con la valutazione del lavoro da eseguire. Le fasi principali sono: Identificazione, classificazione e priorità dei problemi/modifiche: la classificazione sarà: o Correttiva o Adattativa o Perfettiva o Di emergenza: si cerca di ripristinare immediatamente il sistema Il processo che viene effettuato in questa fase è: Si assegna un numero di identificazione al Maintenance (Change) Request L Help Desk classifica il tipo di manutenzione che si deve andare ad effettuare Analizzare la modifica per determinare se accettare, rifiutare oppure valutare ulteriormente accertando la complessità dell intervento. Fare una stima preliminare della dimensione e dell importanza delle modifiche Realizzare un indice di priorità delle modifiche Assegnare la MR ad un team di sviluppatori che si occuperanno di modificare l implementazione del software Durante questa fase, per passare ad una fase successiva si devono fare dei controlli (il processo sia stato determinato, identificato e inserito nel repository). In questa fase si devono produrre dei documenti che specificano le operazioni da effettuare e soprattutto una stima del costo dell intervento. I documenti da produrre sono: o Dichiarazione del problema o di nuovi requisiti o Valutazione del problema o dei requisiti o Classificazione del tipo di manutenzione richiesto o Verifica dei dati o Stima iniziale della risorse da modificare nel sistema esistente Analisi: si effettua una valutazione della Maintenance Request, della documentazione del progetto e del sistema. Si valutano anche le possibili alternative (trade off). Quello che si tira fuori è un piano per il design, l implementazione, test e consegna. Ci si deve rendere conto delle fattibilità dell intervento. Questa fase è divisa in 2 parti: o feasibility analysis: si valutano gli impatti della modifica identificando e stimando le modifiche dall inizio dell intervento. Di solito si cerca di individuare un starting impact set. Si usano tecniche di prototipazione, analisi di requisiti di conversione. Si considerano tecniche di sicurezza, costi di modifica e i benefici, in termini monetari, di ciò che può accadere. Purtroppo però queste tecniche non ci danno l accuratezza massima di valutazione dei costi poiché posso avere dei discovered impact set che non sono stati previsti ma sono stati individuati successivamente mentre alcuni che sono stati individuati e possono risultare dei falsi positivi. Questa fase si chiama impact analysis. o Detail analysis: si passa all analisi dei dettagli sviluppando un piano di implementazione. Si identificano gli elementi da modificare, si dividono le strategie di testing e si realizza un piano di implementazione. Vengono rivisitati gli elementi proposti ed analizzate le fattibilità tecniche ed economiche, identificazione della sicurezza e considerazione dell integrazione 8

9 IS2 -> Capitolo 1 della proposta di cambiamento. Inoltre si deve verificare che tutta la documentazione sia aggiornata ed opportunamente controllata. La documentazione se viene aggiornata, viene mantenuta nella parte di manutenzione e nel momento in cui si rilascia la nuova versione, si rilascia anche la nuova documentazione e viene inserita nel repository. Si deve verificare che le funzioni di testing dell organizzazione siano fornite da una strategia per il testing dei cambiamenti e che il cambiamento programmato può supportare la strategia di testing proposto. Inoltra bisogna rivedere le stime delle risorse e schedulare e verificare la loro accuratezza ed infine rivedere le tecniche per selezionare i problemi importanti e le proposte di cambiamento per implementare la nuova release. Gli output: o Feasibility report per MR o Report dell analisi dettagliata o Aggiornamento dei requisiti (inclusa la lista di tracciabilità) o Lista delle modifiche preliminari o Strategia di test o Piano di implementazione Nel caso in cui la documentazione non c è oppure non è aggiornata, si deve creare facendo reengeneering. Design: usando gli output della fase di analisi, insieme a tutto il sistema esistente e alla documentazione corrente, si definiscono le modifiche da apportare al sistema. Nella fase di processo si vanno ad identificare i moduli software e gli impatti che apporteranno al sistema, si modifica la documentazione, si creano nuovi test case, identificare e creare test di regressione, aggiornare la documentazione e aggiornare la lista delle modifiche. Nella fase di controllo bisogna fare il re-view fatta durante la fase di design, verificare che il design è documentato e che anche le varie fasi sono state ben documentate e si completa la tracciabilità dei requisiti per il design. Gli output: o Lista delle modifiche rivista o Baseline aggiornata o Test plan aggiornato o Detailed analysis rivista o Verifica dei requisiti o Piano di implementazione rivista o Lista dei vincoli documentati e dei rischi Implementazione: l intero sistema viene aggiornato in base alla fase di analisi e design e dovrebbe essere usata per guidare gli sforzi di implementazione. Nella fase di processo si codifica e si effettua lo unit test, si effettua l integrazione e si valutano i rischi. Nella fase di controllo si effettua una ispezione del codice, ci si assicuri che la documentazione di test viene aggiornata, individuare e risolvere i rischi, che il software viene inserito sotto Configuration Managment, verificare la tracciabilità del design del codice. Gli output: o Software aggiornato o Aggiornamento dei vari documenti (sia a livello di design, test e user) o Valutazione dei rischi e degli impatti agli utenti Test di Regressione: si fa il test di sistema per verificare che non ci sono problemi a seguito delle modifiche. L input di questa fase è la documentazione e il sistema aggiornato (che ancora non è messo in esercizio e che è ancora nella macchina di test). Nella fase di processo si effettua il test delle funzionalità, si testano le interfacce, test di regressione, ecc. Nella fase di controllo si testa tutto ciò che è stato introdotto tramite un test funzionale indipendente. Prima del completamento 9

10 IS2 -> Capitolo 1 della fase di testing, bisogna porre il tutto nel Software Configuration Management e far partecipare il cliente durante questa fase per far si che le modifiche siano quelle richieste. Gli output: o Sistema testato e pienamente integrato o Il report del test Testing di accettazione: si effettua un test completo su tutto il sistema. Come input si prendono i report e i documenti prodotti precedentemente. Nella fase di processo si controllano i test di accettazione a livello funzionale, si esegue il test di interoperabilità e il testing di regressione. Nella fase di controllo si eseguono i test di accettazione, report dei risultati del controllo della configurazione funzionale (FCA), si inserisce il tutto nel SCM e viene definita come nuova baseline pronta per essere rilasciata al cliente. Gli output prodotti sono: o la nuova baseline del sistema o il report FCA o il report del test di accettazione (responsabilità del cliente). Consegnare il prodotto: viene rilasciata la nuova versione funzionante del sistema. Si conduce una Phisycal Configuration Audit (PCA), si notifica alla comunità degli utenti, si sviluppa un archivio di versioni del sistema per il backup e si esegue l installazione e il training agli utenti. Nella fase di controllo si sistema il documento PCA, si forniscono i materiali per l accesso degli utenti, inclusa la replica e la distribuzione. Si completano il version description document (VDD), gli aggiornamenti al database della contabilità e si inserisce il tutto nel SCM. Viene prodotto in output un report del PCA e la nuova versione dei documenti. TRACEABILITY MANAGEMENT FOR IMPACT ANALYSIS Introduzione: Software maintenace e impact analysis La manutenzione del software è il processo generale di modifica di un sistema dopo la sua consegna. Le modifiche possono essere semplici cambiamenti per correggere errori di programmazione, o più estensivi quando devono essere corretti errori fatti nella fase di progettazione. I cambiamenti sono fatti modificando i componenti esistenti del sistema e, dove necessario, aggiungendone di nuovi. Quindi al fine di analizzare l impatto di un cambiamento, c è bisogno di individuare le parti del sistema che saranno affette dalla modifica e esaminarle per possibili ulteriori impatti. L analisi dell impatto dovrebbe: Identificare potenziali ripple effects (reazioni a catena) Permettere compromessi tra gli approcci precedentemente considerati Essere eseguita con l aiuto di documentazione estratta dal codice sorgente Considerare la storia dei cambiamenti precedenti, sia quelli avvenuti con successo, che quelli abortiti. In particolare un cambiamento ha impatto non solo sul codice sorgente ma anche sugli artefatti relativi al software come analisi dei requisiti, design e test artefact. Per questa ragione l impact analisys deve essere efficientemente supportata attraverso le informazioni di tracciabilità. La tracciabilità è stata definita come: l abilità di descrivere e seguire la vita di un artefatto in entrambe le direzioni (in avanti e in indietro). Quindi i link di tracciabilità aiutano gli ingegneri del software a capire le relazioni e le dipendenze tra i vari artefatti software. 10

11 IS2 -> Capitolo 1 Prevedere la manutenzione Quando deve essere fatta una modifica bisogna valutare i cambiamenti. Prima di mettere mano al software, bisogna studiare l impatto della modifica ossia cosa può accadere quando si va a modificare quella componente software e cambiare quindi anche i relativi documenti. È una fase abbastanza complessa e per aiutarci possiamo usare degli strumenti di tracciabilità. Bisogna mantenere i legami tra ciò che è la documentazione e ciò che è il software e questo viene fatto tramite la tracciabilità. Bisogna tener conto di tutti gli artefatti che collegano ciò che noi andiamo a modificare. La difficoltà nel fare software maintenance è nel comprendere il sistema e non nell effettuare la modifica soprattutto se vi è una carenza di documentazione. Molto spesso la documentazione non è allineata con il codice e di solito quello che si fa è aggiornare il documento quando vi sono i controlli di qualità. Devo analizzare l impatto di una modifica e dell effetto che essa ha su una componente software ed esaminare i possibili impatti futuri. Per realizzare ciò, devo cercare gli artefatti che potenzialmente potrebbero essere collegati direttamente con quella modifica e se necessario modificare anch essi. Se ho tracciabilità degli artefatti, posso alleggerire il carico di lavoro e vedere il requisito come è stato raffinato, come si è evoluto nella fase di progettazione e come sono legati tra di loro. Mantenere queste informazioni durante la fase di progettazione è molto dispendioso, ma molto utile per una successiva manutenzione. Impact analysis Il processo inizia analizzando le specifiche della change request, il codice del sistema e la documentazione al fine di identificare l insieme iniziale di artefatti software che potrebbero essere intaccati dal cambiamento richiesto. Questo insieme è chiamato Start Impact Set (SIS). Gli artefatti nello SIS sono poi analizzati per identificare altri artefatti che possono essere intaccati dalla modifica. L insieme risultante degli artefatti è chiamato Candidate Impact Set (CIS). Una volta che il cambiamento è stato implementato, si individua l Actual Impact Set (AIS) che è l insieme degli artefatti realmente modificati. Si noti che l AIS non è unico rispetto alla richiesta di cambiamento in quanto il cambiamento può essere implementato in diversi modi. Vado ad aumentare l insieme degli artefatti, si effettuano delle supposizioni come ad esempio cambiare il sequence diagram (questo va fatto solo se si cambia la firma del metodo). Quindi prendo in analisi il sequence diagram e successivamente quando effettivamente effettuo la modifica, si constata se la modifica deve essere fatta o meno al sequence diagram (falsi positivi). L impact analysis è un processo iterativo; in particolare durante l implementazione di un cambiamento, nuovi artefatti impattati dalla modifica non inclusi nel CIS, possono essere scoperti. L insieme di questi 11

12 IS2 -> Capitolo 1 artefatti, chiamato Discovered Impact Set (DIS), rappresenta una sottostima dell impatto. D altro canto, è anche possibile che alcuni artefatti nel CIS non sono impattati dall implementazione della modifica. L insieme di questi artefatti è chiamato False Positive Impact Set (FPIS) e rappresenta una sovrastima dell impatto nell analisi. L unione del CIS e del DIS meno gli artefatti in FPIS dovrebbe risultare nell AIS. Lo scopo del processo di Impact Analysis è di stimare un CIS che sia il più simile possibile all AIS; quindi: Verifica dello starting impact set SIS Creazione del candidate impact set CIS Scrematura del candidate impact set per ottenere l actual impact set; durante questa fase posso trovare falsi positivi oppure altri elementi da arricchire che costituiscono il discovered impact set. Il primo step è quello di identificare lo starting impact set e questa fase è abbastanza dispendiosa. Bisogna mappare il concetto definito in una richiesta di cambiamento nel codice del componente. Bisogna capire come ad esempio il caso d uso studente diventa la classe Studente. Molto spesso i vari approcci al concept assignment sono combinati tra di loro. Per identificare il candidate impact set, devo vedere gli artefatti che potranno impattare dall artefatto di partenza. Bisogna usare le dipendenze degli artefatti per constatare i collegamenti tra gli artefatti. Nel caso in cui questa fase viene realizzata troppo velocemente, posso rischiare di tralasciare qualche artefatto mentre se mi dilungo troppo in questa fase posso includere artefatti che non sono necessari. Quello che si fa è quello di realizzare un analisi di tracciabilità secondo dei criteri per cercare di ridurre il numero di artefatti da piazzare nello starting impact set. Un altra tecnica è quella di effettuare delle valutazioni incrementali. Possono essere definite molte metriche per valutare l accuratezza del processo di impact analisys per esempio: Recall: misura la percentuale degli impatti reali inclusi in CIS (il rapporto tra ); Precision: misura la percentuale di impatti candidati che sono impatti reali (il rapporto tra ). Recall vale 1 quando DIS è vuoto mentre precision vale 1 quando FPIS è vuoto, quindi se CIS coincide con AIS, sia recall che precision valgono 1. Sfortunatamente questo accade molto raramente e quindi c è bisogno di trovare un compromesso tra le 2 metriche. Identificare il SIS: è difficile mappare i concetti definiti nella specifica di richiesta di cambiamento in codice sorgente. Il concept assignment problem è definito come il problema di scoprire concetti human oriented ed assegnarli alle loro implementazioni. Per identificare i concetti o le funzioni del codice sono stati proposti diversi approcci alcuni di questi si basano sull analisi statica, dinamica o a delle combinazioni di queste. In ogni caso il processo di assegnamento dei concetti non può essere completamente automatizzato in quanto i concetti ed i programmi non sono sullo stesso livello di astrazione. Quindi è necessaria l interazione umana. Identificare il CIS: un semplice meccanismo per ridurre il numero di falsi positivi è di considerare la distanza dagli artefatti. In questo modo solo gli artefatti con la distanza dall artefatto modificato minore uguale ad un fissato valore vengono considerati. Associare link di tracciabilità con una semantica specifica o indicare la probabilità di un impatto indiretto è anche utile per ridurre il CIS. Un altro modo per ridurre il numero di falsi positivi è di definire regole di propagazione e usare queste regole per navigare il grafo di raggiungibilità al fine di definire il CIS. Un approccio simile consiste nel propagare eventi da un artefatto agli artefatti ad esso dipendenti. Un altro modo è di analizzare in modo incrementale il CIS. Per ogni artefatto nel SIS l ingegnere del software visita tutti gli artefatti che interagiscono con esso; un altra idea per definire il CIS in modo incrementale si basa sul fatto che l implementazione di una richiesta di cambiamento modificherà 12

13 IS2 -> Capitolo 1 l insieme dei documenti partendo da quelli di livello più alto e propagando il cambiamento verso il basso nell insieme completo di documenti. TRACCIABILITÀ Tracciabilità orizzontale e verticale Un supporto appropriato per la tracciabilità può determinare l efficienza del processo di impact analysis. La prima dimensione che consideriamo è relativa al supporto per la tracciabilità verticale ed orizzontale. Verticale: si riferisce all abilità di tracciare le dipendenze degli artefatti seguendo un modello Orizzontale: si riferisce all abilità di tracciare artefatti tra modelli differenti. Molti degli approcci che forniscono informazioni di tracciabilità verticale sono concentrati sugli artefatti di codice sorgente. In questo contesto tecniche di analisi del codice sorgente sono usate per individuare e catturare le dipendenze tra le varie componenti. Le tecniche di reverse engineering permettono di identificare dipendenze tra moduli a livello dell architettura. L identificazione delle componenti di codice sorgente impattate da una proposta di cambiamento è generalmente considerato l output più importante del processo di impact analysis. La tracciabilità verticale fornisce solo una vista limitata degli artefatti potenzialmente impattati dalla proposta di cambiamento. Sistemi software complessi richiedono la produzione di modelli software con molti livelli di astrazione e gli artefatti relativi appartenenti ai diversi livelli hanno bisogno di essere modificati consistentemente. Come conseguenza molti approcci estendono la tracciabilità verticale con quella orizzontale permettendo quindi la gestione delle dipendenze tra modelli a differenti livelli di astrazione. È importante notare che la granularità di un artefatto gioca un ruolo importante nella definizione dei link di tracciabilità tra gli artefatti. In verità la decomposizione degli artefatti in sotto-artefatti consente un impact analysis più precisa e riduce il tempo necessario a identificare l esatta parte dell artefatto impattato dalla modifica. Tracciabilità strutturale e knowledge-based La seconda dimensione considera la natura delle informazioni usate per ricavare i link di tracciabilità. Distinguiamo tra structural e knowledge-based link. Structural link: possono essere naturalmente derivati analizzando gli artefatti rispetto alla sintassi ed alla semantica del linguaggio formale in cui sono espressi. Per esempio relazioni di uso di eredità e composizione tra le classi di un programma object oriented possono essere facilmente ottenute analizzando il codice sorgente in accordo alla grammatica formale di linguaggio di programmazione. D altro canto la tracciabilità knowledge-based link si riferisce alla dipendenze tra artefatti che non possono essere automaticamente derivate analizzando il codice sorgente o altri artefatti sviluppati in accordo ad una sintassi ed una semantica formale. 13

14 IS2 -> Capitolo 1 Tracciabilità implicita ed esplicita La terza dimensione considerata si riferisce alla rappresentazione dei link ed in particolare una distinzione può essere fatta tra approcci che usano link espliciti per rappresentare una dipendenza e approcci che li ricavano quando richiesti. È anche possibile considerare approcci ibridi in cui alcuni tipi di link sono memorizzati esplicitamente mentre altri tipi vengono ricavati quando necessari. L analisi del codice sorgente e le tecniche di reverse engineering memorizzano le dipendenze dei programmi in alcune strutture di rappresentazione e le attraversano per dedurre le dipendenze ad un alto livello di astrazione. PROBLEMI DI TRACCIABILITA Uno dei maggiori problemi della gestione della tracciabilità è quello di ricavare i link knowledge-based. In verità mentre tecniche per ricavare structural link sono mature e automatizzate, quelle per ricavare link knowledge-based richiedono la validazione dell utente. Le informazioni di tracciabilità esplicite possono diventare velocemente obsolete a causa della naturale evoluzione del software. In particolare la matrice di tracciabilità può diventare incompleta o includere link non più validi. Questa situazione è dovuta al fatto che mantenere le informazioni di tracciabilità aggiornate è tedioso e dispendioso in termini di tempo e spesso è sacrificato per dare spazio al lavoro arretrato. Recovery Molti metodi sono stati proposti per ricavare i link di tracciabilità tra artefatti software di tipi differenti. Gli approcci proposti possono essere classificati in: 1. Basati su euristiche: per esplorare le convenzioni sui nomi e mappare il codice sorgente in modelli di alto livello, vengono usate espressioni regolari. Regole più complicate possono essere derivate considerando documenti di testo scritto in linguaggio naturale. In particolare è possibile definire regole che sintatticamente fanno corrispondere a termini della parte testuale degli artefatti richiesti, elementi in un modello ad oggetti. Quindi le relazioni di tracciabilità possono essere definite quando viene trovato un match. Recentemente a questo scopo sono state proposte l uso di tecniche ontologiche. Viene anche usata l analisi dinamica; in particolare link di tracciabilità tra i requisiti ed il codice possono essere ricavati monitorando il codice sorgente per registrare quali classi vengono usati in un determinato scenario. 2. Basati su information retrieval (IR): l idea alla base dell uso di metodi di IR per ricavare link di tracciabilità consiste nel fatto che la maggior parte della documentazione che accompagna grossi sistemi software è formata da documenti di testo espressi in linguaggio naturale e la similarità tra parti di questi documenti di artefatti diversi possono evidenziare la presenza di link di tracciabilità. L accuratezza delle informazioni così ricavate può essere migliorata tramite il training di link corretti o dal feedback fornito dagli utenti durante la classificazione dei link candidati. 3. Basati su data mining (DM): tecniche di DM usano la storia di repository CVS per individuare classi, file e funzioni logicamente accoppiati. Questa metodologia investiga sullo sviluppo delle varie classi misurando il tempo che intercorre tra l aggiunta di nuove classi e la modifica di quelle esistenti, mantenendo attributi come l autore, la data del cambiamento, ecc. Ad un certo punto del processo, abbiamo la necessità di recuperare alcuni link necessari. In pratica quando si propone un cambiamento bisogna tracciare l impatto sugli altri requisiti e sul progetto del sistema. La tracciabilità è una specifica che riflette la facilità di trovare requisiti relativi. Esistono tre tipi di tracciabilità dell informazione: 14

15 IS2 -> Capitolo 1 Tracciabilità della sorgente: collegamenti tra i requisiti, il loro fondamento logico e gli stakeholder che li hanno indicati. Quando viene proposta una modifica, si usano queste informazioni per trovare e consultare gli stakeholder a proposito del cambiamento. Tracciabilità dei requisiti: collegamenti tra requisiti dipendenti all interno del documento. Si usano queste informazioni per valutare quanti requisiti sarebbero influenzati da un cambiamento proposto e quale sarebbe l estensione delle conseguenti modifiche che potrebbero essere necessarie. Tracciabilità del progetto: collegamenti tra i requisiti e i moduli del progetto in cui sono implementati. Si usano tali informazioni per valutare l impatto delle modifiche proposte sul progetto e sull implementazione. Le informazioni di tracciabilità sono spesso rappresentate da matrici, che mettono in relazione i requisiti con gli stakeholder, con gli altri requisiti o i moduli del progetto. In una matrice di tracciabilità ogni requisito viene inserito in una riga e in una colonna, se esistono dipendenze tra i requisiti, vengono registrate nella cella di intersezione riga/colonna. La gestione dei requisiti richiede un supporto automatizzato, come gli strumenti CASE 1 necessari in alcune fasi del processo e che dovrebbero essere scelti durante la fase di pianificazione. Memorizzazione dei requisiti: i requisiti dovrebbero essere memorizzati in una memoria dati sicura, gestita e accessibile a tutti coloro che sono coinvolti nel processo di ingegneria dei requisiti. Gestione delle modifiche: il processo di dei requisiti viene semplificato se è disponibile un supporto attivo degli strumenti. Gestione della tracciabilità: il supporto alla tracciabilità permette di scoprire i requisiti correlati; alcuni strumenti utilizzano tecniche del linguaggio naturale per trovare le possibili relazioni tra i requisiti. LINK EVOLUTION Il software evolve così come i link di tracciabilità possono diventare obsoleti. La matrice di tracciabilità può diventare incompleta (mancanza di nuovi link corretti) oppure può includere un numero di link non più validi. Bisogna analizzare la somiglianza di due artefatti, vedere se esiste un link tra A e B. Assumiamo che se 2 artefatti sono tracciati, questi devono avere delle somiglianze testuali. Prendo i link, calcolo la somiglianza. Nel momento in cui la somiglianza non rispetta i miei limiti, si avvisa che non vi è una somiglianza testuale a causa di un errore nel tracciare i link. GESTIONE DELLE MODIFICHE AI REQUISITI La gestione delle modifiche ai requisiti deve essere applicata a tutti i cambiamenti proposti. La figura mostra i tre stadi principali in un processo di gestione delle modifiche: analisi dei problemi e specifica delle modifiche: il processo inizia con l identificazione dei problemi o, a volte, con una proposta specifica di cambiamento. Durante tale stadio viene verificata la validità del problema o della proposta. I risultati di questa analisi sono consegnati a chi ha richiesto la modifica e a volte viene formulata una proposta di cambiamento più precisa. Analisi e stima dei costi della modifica: viene valutato l effetto della modifica proposta, usando le informazioni di tracciabilità e la conoscenza generale dei requisiti di sistema. Il costo della modifica viene valutato in base a quante modifiche dovrebbero essere fatte al documento dei requisiti e, se opportuno, al progetto di sistema e alla sua implementazione. Una volta completata questa fase si procede a valutare se fare o meno questa modifica. 1 Computer Aided Software Engineering (ingegneria del software assistita dal computer). Comprende una vasta gamma di programmi diversi per aiutare le attività di processo del software, quali analisi dei requisiti, analisi dei modelli di sistema, il debugging e il testing. 15

16 IS2 -> Capitolo 1 Implementazione della modifica: vengono modificati il documento dei requisiti e, dove necessario, il progetto del sistema e la sua implementazione. Come per i programmi, la modificabilità di un documento si ottiene minimizzando i riferimenti esistenti e creando le sue sezioni più modulari possibile, in modo che singole sezioni possono essere modificate senza incidere sulle altre presenti nel documento. SOFTWARE RE-ENGINEERING È la fase di riorganizzazione dei cambiamenti dei sistemi di software esistenti, senza cambiarne la funzionalità per renderli più manutenibili. Gli obiettivi sono: spiegare perché la riprogettazione di software è una scelta cost-effective per l'evoluzione del sistema descrivere le attività coinvolte nel software nel processo di riprogettazione distinguere tra software e re-ingegnerizzazione dei dati per spiegare i problemi di quest ultima RE-INGEGNERIZZAZIONE DEL SISTEMA Il processo di evoluzione del sistema richiede la comprensione del programma che deve essere modificato e la successiva implementazione delle modifiche. Per semplificare i problemi della modifica dei sistemi ereditati, una società può decidere di reingegnerizzare il software che consiste nella re-implementazione dei sistemi ereditati per renderli più manutenibili; può richiedere una nuova documentazione, la riorganizzazione e la ristrutturazione del sistema, la traduzione in un linguaggio di programmazione più moderno e l aggiornamento delle strutture e dei valori dei dati di sistema. Le funzionalità del software non sono modificate e normalmente l architettura del sistema resta la stessa. Quando effettuare re-ingegnerizzazione Bisogna effettuare la fase di re-ingegnerizzazione quando i cambiamenti di sistema sono maggiormente confinati verso le parti del sistema che re-ingegnerizza quella parte, quando l hardware o il supporto software diventano obsoleti e quando i tool che supportano la ri-strutturazione sono disponibili e non può essere fatto del tutto manuale. Vantaggi di riprogettazione La re-ingegnerizzazione ha due vantaggi chiave rispetto ad approcci di evoluzione del sistema più radicali. Si riduce il rischio: c'è un rischio alto in sviluppo del software nuovo. Ci possono essere problemi di sviluppo, problemi di personale e di specifica. La maggior delle volte, durante la fase di riprogettazione, si può incorrere in problemi legati al fatto che il codice è vecchio. Riduce il costo: il costo di riprogettazione spesso è significativamente più basso dei costi di sviluppo di un nuovo software. Business di una riprogettazione di un processo Legato con processi di business ri-progettati per renderli più reattivi e più efficienti. Spesso si deve essere fiduciosi sull'introduzione di un nuovo sistema di computer per supportare i processi aggiustati. Si può forzare il software re-engineering come il rilascio di sistemi progettati per supportare processi esistenti. 16

17 IS2 -> Capitolo 1 Ingegnerizzazione diretta e riprogettazione La distinzione critica tra re-ingegnerizzazione e lo sviluppo di nuovo software è il punto di partenza dello sviluppo: anziché iniziare da una specifica scritta, il vecchio sistema funge da specifica per il nuovo sistema. L ingegnerizzazione diretta: inizia da una specifica di sistema e richiede la riprogettazione e l implementazione di un nuovo sistema; La re-ingegnerizzazione: comincia con un sistema esistente e il processo di sviluppo è basato sulla comprensione e trasformazione del sistema originale. Il processo di riprogettazione La figura illustra il processo di re-ingegnerizzazione: l input al processo è un programma ereditato, il suo output è una versione strutturata e modularizzata dello stesso programma e le attività sono quelle di seguito elencate: 1. Traduzione del codice sorgente: si converte il vecchio linguaggio di programmazione in una versione più moderna o in un linguaggio diverso. 2. Reverse engineering: il programma viene analizzato e vengono estratte informazioni utili a documentare la sua organizzazione e la sua funzionalità. 3. Perfezionamento della struttura del programma: la struttura di controllo viene analizzata e modificata per renderla più semplice da leggere e comprendere. 4. Modulazione del programma: si raggruppano le parti correlate del programma e, dove appropriato, viene rimossa la ridondanza. In certi casi questo passo richiede una trasformazione dell architettura del sistema. Un sistema sviluppato per un architettura centralizzata viene trasformato in un sistema per un architettura distribuita. 5. Re-ingegnerizzazione dei dati: si modificano i dati elaborati dal programma perché riflettono le modifiche apportate al programma stesso. Non tutte le fasi illustrate nella figura sono sempre richieste dal processo di re-ingegnerizzazione: la traduzione del codice sorgente può non essere necessaria se il linguaggio sorgente utilizzato non subisce variazioni; la re-ingegnerizzazione dei dati è necessaria solo se durante il processo le strutture dati del programma vengono modificate. La re-ingegnerizzazione del software però comprende sempre almeno una parte di ristrutturazione del programma. 17

18 IS2 -> Capitolo 1 Approcci di riprogettazione Modificare un sistema informativo aziendale già esistente è un processo molto tedioso. Ci si lavora fino a quando non va in esercizio e vi lavorano anche un numero elevato (fino a centinaia di persone) di consulenti software. Gli approcci, visti in figura, fanno aumentare il costo man mano che si va avanti. Ovvero aumentano da sinistra verso destra, quindi la traduzione del codice sorgente è la soluzione più economica, mentre la reingegnerizzazione come parte di una migrazione architetturale è quella più costosa. Diversi fattori influenzano i costi di re-ingegnerizzazione: 1. La qualità del software da re-ingegnerizzare: più è bassa la qualità del software e della documentazione associata, più sono alti i costi di re-ingegnerizzazione. 2. Il supporto di strumenti disponibili per la re-ingegnerizzazione: normalmente non è economico reingegnerizzare un software se non si utilizzano strumenti CASE per automatizzare la maggior parte delle modifiche del programma. 3. Le dimensione della conversione dati richiesta: se la re-ingegnerizzazione richiede la conversione di un grande quantitativo di dati, i costi del processo aumentano significativamente. 4. La disponibilità di uno staff esperto: se non si coinvolge nel processo di re-ingenerizzazione lo staff responsabile alla manutenzione del sistema, i costi aumentano perché coloro che procederanno nella fase di re-ingegnerizzazione impiegheranno molto tempo nella comprensione del programma. Il principale svantaggio nella re-ingegnerizzazione del software è l esistenza di limiti al grado di perfezionamento raggiungibile: ad esempio non è possibile convertire un sistema scritto usando un approccio funzionale in un sistema orientato agli oggetti. Traduzione di codice sorgente Comporta la conversione di codice da un linguaggio (o versione di linguaggio) ad un altro e.g. LINGUAGGIO FORTRAN a C. Questo deve essere necessario a causa di: Aggiornamento di piattaforma hardware Scarsità di abilità di personale in grado di comprendere il linguaggio Cambi di politiche organizzative È realistico solo se disponibile un traduttore automatico. Può non esserci un mapping diretto. Reverse Engineering Viene analizzato il software con una vista atta alla comprensione del design e delle specifiche. Questa fase è parte di un processo di riprogettazione ma può essere usato anche per ri-specificare un sistema e per la 18

19 IS2 -> Capitolo 1 ri-implementazione. Bisogna creare una base dati di programma e generare informazioni da questo. In questa fase possono essere usati tool di comprensione (browser, ecc.). Spesso precede la fase di re-engineering Miglioramento della struttura di un programma La manutenzione tende a corrompere la struttura di un programma. Diviene più difficile da capire. Il programma può essere ristrutturato automaticamente per rimuovere rami incondizionati. Le condizioni possono essere semplificate per renderle più leggibili. Ristrutturazione automatica di un programma Ho un program generator che va a modificare il grafo e a riprodurre il codice. Diciamo che questo potrebbe essere già incluso nei compilatori ma gli obiettivi sono diversi e per questo motivo non viene fatto. Quando produco codice oggetto, nel grafo vado a metterci le mani ma sono diverse da quelle che vado a fare in extraction. Problemi di ristrutturazione I problemi di ristrutturazione sono: Perdita di commenti perché bisogna capire dove questi devono essere associati, capire l esatta posizione. Perdita della documentazione. Richieste computazionali pesanti. La ristrutturazione non aiuta quando c è poca modularizzazione in quanto le componenti collegate sono disperse in tutto il codice. La comprensibilità di programmi data driven potrebbe non essere migliorata dalla ristrutturazione. Modularizzazione dei programmi Il processo di ri-organizzare di un programma fa in modo che le parti collegate di un programma siano raccolte insieme in un singolo modulo. Di solito è un processo manuale che è eseguito da un programma di ispezione e di ri-organizzazione. Tipi di modulo astrazioni di dati: tipi di dati astratti dove le strutture dati e le operazioni associate sono raggruppate 19

20 IS2 -> Capitolo 1 moduli hardware: tutte le funzioni richieste per interfacciarsi con un'unità hardware moduli funzionali: moduli che contengono funzioni che eseguono task collegati moduli di supporto a processi: moduli dove le funzioni supportano un processo di business o un frammento di processo. Recupero dell astrazione dei dati Molti sistemi legacy usano tabelle condivisi e dati globali per risparmiare memoria. Questo causa problemi in quanto i cambiamenti hanno un impatto molto ampio nel sistema. I dati condivisi globalmente possono essere convertiti in oggetti o in ADT (strutture dati): analizzare le aree di dati comuni per identificare astrazioni logiche; creare un ADT o un oggetto per queste astrazioni; usare un browser per trovare tutti i riferimenti ai dati e rimpiazzarli con i riferimenti ai dati astratti. Riprogettazione di dati Il data re-engineering coinvolge l analisi e la riorganizzazione delle strutture dati (e qualche volta dei valori) in un programma. Può essere parte del processo di migrazione da un sistema basato sui file ad un sistema basato su DBMS o di cambiamento da un DBMS ad un altro. L'obiettivo è creare un ambiente managed data. Approcci a riprogettazione di dati Approcci Pulizia di dati Estensione di dati Migrazione di dati Descrizione I record di dati e valori sono analizzati per migliorare la loro qualità. I duplicati sono rimossi, informazioni ridondanti sono cancellate, viene applicato un formato a tutti i record. Questo non ne dovrebbe richiedere normalmente cambi di programma associati. In questo caso, i dati e programmi associati sono ri-pianificati per rimuovere i limiti sull'elaborazione dati. Questo può richiedere cambi a programmi e aumenti lunghezze dei campi, modifiche ai limiti superiori delle tabelle, ecc. I dati stessi possono quindi dover essere riscritti per adattarsi al programma modificato. In questo caso, i dati sono passati al controllo di un DBMS moderno. I dati possono essere conservati in file separati o possono essere gestiti da un più vecchio tipo di DBMS Problemi dei dati Gli utenti finali vogliono dati sulle loro macchine desktop piuttosto che in un file system. Hanno bisogno di essere capace di scaricare questi dati da un DBMS. I sistemi possono dover elaborare molto più dati di quelli originalmente pensati dai loro progettisti. Dati ridondanti possono essere conservati in formati diversi e in posti diversi nel sistema (non è una cosa facile). 20

21 IS2 -> Capitolo 1 Possono sorgere a tal motivo diversi problemi quali: problemi di nomi dei dati: i nomi possono essere difficili da capire. Gli stessi dati possono avere diversi nomi in programmi diversi. problemi di lunghezza di campi: allo stesso oggetto possono essere assegnate lunghezze diverse in programmi diversi. problemi di organizzazione di record: i record che rappresentano la stessa entità possono essere organizzati differentemente in programmi diversi. nessun dizionario di dati. Inconsistenza sui valori dei dati Inconsistenza dei valori di default: programmi diversi assegnano valori predefiniti diversi agli stessi dati logici. Questo causa problemi per altri programmi. Il problema si presenta quando valori mancanti sono assegnati a valori di default che sono validi. I dati mancanti non possono essere scoperti. Unità inconsistente: le stesse informazioni sono rappresentate in unità diverse in differenti programmi. Per esempio, negli Stati Uniti o nel Regno Unito i dati di peso possono essere rappresentato in libbre nei più vecchi programmi ma in chilogrammi in più recente sistemi. Un altro problema notevole di questo tipo è sorto in Europa con l introduzione di una sola moneta europea. I sistemi legacy sono stati scritti per trattare con le valute nazionali e c è stato il bisogno di convertire i dati in euro. Regole di validazione inconsistenti: programmi diversi applicano differenti regole di validazione di dati. I dati scritti da un programma possono essere riusati da un altro. Questo è un particolare problema per gli archivi di dati i quali non sono stati aggiornati con il cambiamento di regole di validazione. Rappresentazioni semantiche inconsistenti: i programmi assumono alcuni significati nel modo in cui gli oggetti sono rappresentati. Per esempio, dei programmi possono assumere che il testo maiuscolo vuole dire un indirizzo. I programmi possono usare le convenzioni diverse e possono rifiutare quindi dati che sono semanticamente validi. Inconsistenza nella gestione dei valori negativi: alcuni programmi rifiutano valori negativi per entità che devono essere sempre positive. Comunque, altri possono accettare questi come valori negativi o fallire nel riconoscimento in negativo e convertirli in valore positivo. Conversione di dati La riprogettazione di dati può comportare cambio di strutture dati senza cambiare il valore di dati. La conversione dei valori di dati è molto costosa. Programmi special purpose devono essere scritti per eseguire la conversione. Il processo di riprogettazione di dati 21

22 IS2 -> Capitolo 2 CAPITOLO 2: SOFTWARE CONFIGURATION MANAGMENT CONFIGURATION MANAGMENT Lo scopo di usare un Software Configuration Managment (SCM) nasce dal problema che molte persone hanno la necessità di lavorare su software che sta cambiando. Bisogna tenere in considerazione più di una versione del software e soprattutto bisogna tener presente che il software deve operare su macchine differenti e SO differenti. Lo scopo principale di un SCM è quello di coordinare l evoluzione del sistema software e controllare gli aumenti dei costi man mano che il sistema cambia. Ovvero è la disciplina che gestisce e controlla i cambiamenti nell evoluzione nel sistema software. Il sistema Configuration management automatizza l identificazione della versione la sua memorizzazione e recupero, Che cos è un SCM? È un insieme di discipline che accompagnano il processo ingegneristico del software sulla base di linee guida. Spiega le discipline e le tecniche di inizio, valutazione e controllo durante una produzione software e dopo il processo di creazione. Le linee guida vengono descritte nei documenti IEEE 828 e IEEE SCM è una funzione progettuale con degli obiettivi da realizzare. Attività del Configuration Management Identificazione dei configuration item: i componenti di un sistema, i loro lavori e le loro versioni sono identificati e etichettati univocamente. Gli sviluppatori identificano i configuration items dopo aver accettare il progetto. Questa fase è simile alla fase di identificazione deglio oggetti nell Analisi. Promotion management: la creazione delle versioni per altri sviluppatori. Gli sviluppatori creano le promotions per costruire configuration item accessibili per revisione. Una volta che la promotion è stata creata e memorizzata nel configuration management system, gli sviluppatori interessati alla promotion possono estrarla del repository. Gli sviluppatori non interessati alla promotion riprendono il loro lavoro dall inizio. Release management: la creazione di versioni per i clienti e gli utenti. Una release è creata per offrire addizionali funzionali o per individuare bug critici. Se viene introdotta una release che produce malfunzionamente semplicemente lo si risolve tornando ad una release precedente. Change management: i cambiamenti al sistema e le release per gli utenti sono controllati per assicurare la consistenza con gli obiettivi del progetto. La creazione di nuove promotion e release è guidata dalla richiesta di modifica (change request). I clienti definiscono nuove richieste o sviluppi portando ad una nuova implementazione, che causa una nuova release del sistema. Il processo di Change request varia in base alla formalità e complessità che il progetto vuole raggiungere. In entrambi i casiil processo include i seguenti passi: o La modifica, identifica i fault generati dalla nuova caratteristica. Questa può essere fatta da chiunque inclusi utenti o sviluppatori o La richiesta è valutata partendo dagli obbiettivi che si vogliono raggiungere. In un sistema grande questa è fatta dal control board. In un sistema piccolo è fatta dal project manager. Questa fase include i costi di analisi e valutazione degli impatti della modifica nel resto del sistema. o Seguendo la valutazione, la richiesta può essere accettata o rifiutata. o Se è accettata, la modifica e pianificata, priorizzata e assegnata ad uno sviluppatore e implementata. 22

23 IS2 -> Capitolo 2 o L implementazione della modifica è ufficiale. Questa è accettata dal team del controllo della qualità o da chiunque è responsabile per la gestione della release. Branch management: lo stato di un componente individuale e le richieste di modifiche sono archiviate. Permette agli sviluppatori di tenere traccia di ogni componente e di tutto il sistema. Vari configuration items sono revisionati finchè la modifica è completa. Generalmente gli sviluppatori lavorano a miglioramenti concorrenti. Variant management(vedi sotto): le versioni selezionate per le release sono validate per assicurare la completezza, la consistenza e la qualità del prodotto. Svolto da un team specializzato. Terminologia(pagina 536 Bruegge) Configuration item: è un prodotto di lavoro o un pezzo di software che è trattato come entità singola per l ambiente del configuration management Change request: è una proposta formale pubblicata da un utente o da uno sviluppatore per richiedere una modifica in un configuration item. L autore della richiesta specifica il configuration items a cui si vuole applicare la richiesta, la versione, il problema da risolvere e una possibile soluzione. Il questo caso un processo formale di modifica, i costi e i benefici della modifica sono valutati prima che la modifica sia accettata o rifiutata. In entrambi i casi, la decisione è registrata con la richiesta di modifica. Version: identifica lo stato di un configuration item o la configurazione in un punto ben definito del progetto. Versioni successive portano alla correzione di fault, aggiunta di nuove funzionalità e se necessario la rimozione o sostituzione di vecchie e obsolete funzionalità. Variants: sono version che possono coesistere Configuration: è un insieme consistente di versioni di configuration item Promotion: è una versione che è stata resa disponibile dagli sviluppatori di un progetto. Denota una versione stabile e puo essere usata per essere valuta dagli altri sviluppatori. Release: è la versione che è stata resa disponibile al cliente o agli utenti. Puo essere utilizzata dagli utenti per verificarne le qualità. Software library: è dove vengono archiviate le versioni e utilizzate per tenere traccia delle modifiche Repository: è una libreria di release Master directory: è una libreria di promotion CM Aggregate: è una composizione di configuration item Baseline E un prodotto o un insieme di specifiche che è stato formalmente testato e approvato dal manager responsabile del progetto, e che sarà utilizzato come base per gli sviluppi futuri e può essere modificata solo con procedure formali per il controllo delle modifiche. Versioni che intendono coesistere sono chiamate varianti. Numeri di versione Nello schema a tre cifre: Le prime cifre indicano la versione principale (la versione con maggior funzionalità e la miglior interfaccia utente) Le seconde cifre indicano la versione minore e indicano che sono state aggiunte alcune funzionalità rispetto alla versione principale Le ultime cifre indicano le revisioni (sono stati corretti bug o effettuate delle correzioni) 23

24 IS2 -> Capitolo 2 Versione è lo stato del progetto in un momento ben definito. E normalmente associato con una completa compilazione del progetto. Differenti versioni, normalmente, hanno diverse funzionalità Le revisioni sono le modifiche ad una versione che correggono errori o imperfezioni di codice, senza incrementarne le funzionalità. Una release è una versione che è stata resa disponibile all esterno (agli utenti). È la formale distribuzione di una versione approvata. Gestione delle modifiche Gestire le modifiche significa gestire le richieste di modifica e in generale una richiesta di modifica porta alla creazione di una nuova release. Un processo generale di modifica prevede i seguenti passi: inizia con la richiesta (può essere fatta da chiunque, sia utenti e sviluppatori) che successivamente viene valutata se interferisce con gli obiettivi del progetto. Secondo i risultati della valutazione, la modifica viene accettata o rifiutata e se è accettata, la modifica è assegnata ad uno sviluppatore e implementata. Infine la modifica implementata viene verificata. La complessità del processo della gestione delle modifiche varia con il progetto. Con piccoli progetti possono essere eseguite richieste di modifica informalmente e velocemente mentre con progetti complessi si richiedono richieste di modifica dettagliate e approvate ufficialmente da uno o più manager. Esistono due tipi di cambiamenti: 1) Promotion: è modificato il software limitatamente al gruppo di sviluppo 2) Release: le modifiche sono visibili a tutti, il software viene rilasciato agli utenti Branch managment Mentre il software è rilasciato in un modo sequenziale, lo sviluppo di feature differenti può essere fatto da team differenti che operano concorrentemente e poi unite in una singola versione. Un BRANCH identifica un percorso di sviluppo concorrente che richiede il CM indipendente. La sequenza delle versioni creata da ogni team è un Branch che è indipendente dalle versioni create dagli altri team. In generale se non ci sono dei vincoli, i Branch possono divergere a tal punto che non possono essere più uniti. 24

25 IS2 -> Capitolo 2 Esistono alcune euristiche che possono essere usate per ridurre questi rischi: - Identificare probabili sovrapposizioni: prima del design e dell implementazione, gli sviluppatori possono anticipare quali sovrapposizioni possono occorrere. Queste informazioni sono quindi usate per specificare vincoli per limitare questo fenomeno; - Merge frequenti: le policy di CM possono richiedere agli sviluppatori che lavorano su un branch di unire quest ultimo con l ultima versione del main trunk frequentemente. - Comunicare possibili conflitti: sebbene i team che lavorano su branch differenti hanno bisogno di essere indipendenti, possono anticipare i conflitti che si possono verificare in un merge futuro e comunicare questo agli altri team. - Minimizzare i cambiamenti del main trunk: una buona policy di CM è quella che prevede di effettuare solo correzioni di bug del main branch ed effettuare tutti gli altri cambiamenti in un branch di sviluppo. - Minimizzare il numero di branch: abusare di questa tecnica può rendere meno efficiente lo sviluppo di un applicazione. Variant managment Le varianti sono le versioni che sono progettate per coesistere, ad esempio varianti diverse per piattaforme diverse. Un sistema ha multiple varianti quando supportano differenti sistemi operativi e differenti piattaforme hardware. Un sistema ha varianti multiple quando è distribuito con differenti livelli di funzionalità. Ci sono 2 approcci per gestire le varianti: Redundant team: ad ogni team è assegnata una variante. Ogni team è responsabile per il design, implementazione ed il testing della singola variante. Un piccolo numero di configuration item è condiviso tra le varianti. Single project: si progetta un sotto sistema di decomposizione che massimizza il codice che è condiviso tra le varianti (core sub-systems). Per piattaforme multiple, i core sub-system sono a livello più basso tra i sottosistemi. Per livelli multipli di funzionalità si racchiudono gli incrementi di funzionalità prevalentemente in sottosistemi indipendenti. L opzione redundant team conduce molti piccoli progetti che condividono specifiche richieste. 25

26 IS2 -> Capitolo 2 L opzione single project conduce ad un vasto singolo progetto dove moti team condividono il centro del sottosistema. SOFTWARE CONFIGURATION MANAGMENT PLANNING Il SCMPlanning inizia durante le prime fasi di un progetto. Il risultato del SCMPlanning è il software configuration managment plan SCMP che può essere esteso o revisionato durante il resto del progetto. Questo documento può seguire lo standard pubblico IEEE 828 o uno standard interno. Un Software Configuration Management Plan quindi: definisce i tipi di documenti che devono essere gestiti e lo schema per denominarli. definisce chi si prende la responsabilità per le procedure del CM e la creazione delle baseline. definisce le politiche per il controllo delle modifiche e la gestione delle versioni. descrive i tools che dovrebbero essere usati per assistere al processo del CM e qualsiasi limitazione nel loro utilizzo. definisce il configuration management database usato per salvare le informazioni di configurazione. Outline di SCMP 1. Introduzione: descrive lo scope, il fine, le parole chiavi e i riferimenti 2. Management (CHI): identifica i responsabili e le autorità per realizzare le attività programmate di CM 3. Activities (COSA): identifica le attività da eseguire per la realizzazione del progetto 4. Schedule (QUANDO): stabilisce la sequenza e il coordinamento delle attività di SCM con le milestone 5. Risorse (COME): identifica i tool e le tecniche richieste per l implementazione del SCMP. 6. Maintenance: identifica le attività e le responsabilità su come il SCMP deve essere tenuto aggiornato durante il ciclo di vita del progetto. Ruoli di un CM Configuration manager: è responsabile per l identificazione dei configuration item. Può anche essere responsabile per la definizione delle procedure e la creazione di promotion e release. Change control board member: è responsabile per l approvazione o meno delle richieste di cambiamento. Developer: crea le promotion derivate dalla richiesta di cambiamento o le normali attività di sviluppo. Controlla i cambiamenti e risolve i conflitti. Auditor: è responsabile per la selezione e la valutazione di promotion per rilasciare e assicurare la consistenza e la completezza della release. Valutazione di un cambiamento Specifica le analisi richieste per determinare l impatto di una proposta di cambiamento e le procedure per la revisione dei risultati dell analisi. 26

27 IS2 -> Capitolo 2 27

28 Lo sviluppo software guidato dai test: una esperienza con JUnit ed Eclipse Versione 1.2 (Aprile 2008) Vittorio Scarano Dipartimento di Informatica ed Applicazioni Università di Salerno, 84081, Baronissi (SA) Italy. 1 Introduzione Tra le metodologie per lo sviluppo di software, è stata proposta recentemente una disciplina dal nome di Extreme Programming (XP), basata sui valori di semplicità, comunicazione, feedback (testing) e coraggio. L obiettivo principale di XP è quello di organizzare lo sviluppo di software in maniera da escludere la realizzazione di prodotti semilavorati diversi da quelli strettament necessari. In un certo senso, la XP si propone di mantenere il processo di produzione all interno dei due binari di efficacia e della controllabilità. Evitare, quindi, di produrre ponderosa documentazione di analisi e di specifiche formali ed, allo stesso tempo, produrre software con un preciso ambiente di controllo e di feedback, sia dal prodotto software stesso (attraverso fasi di testing) che dal committente e utilizzatore del prodotto. Il dibattito all interno della comunitaà scientifica sulla applicabilità di questa metodologia in tutti i contesti è tuttora aperto; probabilmente l utilizzo di questa metodologia all interno di qualsiasi tipo di progetto ed in qualsiasi situazione rappresenta una esagerazione. La utilità di Extreme Programming sembra, però, riconosciuta all interno di progetti di dimensione medio-piccole, con scadenze pressanti e necessità di adeguamento a situazioni estremamente dinamiche. All interno delle prassi consolidate(le cosiddette core practices) proposte da XP, particolarmente interessante è la enfasi sullo Sviluppo guidato dai test, il cosiddetto Test-Driven Development. Infatti, anche tra i commenti più critici a cui è soggetta la XP, si riscontra un convergente giudizio positivo sulla pratica di sottoporre il proprio software in maniera strutturata e continua a test di sviluppo 1. 1 Una altra prassi universalmente giudicata in maniera positiva è la cosiddetta pair programming, cioè la programmazione in coppie di programmatori, cosa che permette (da esperimenti effettuati) la produzione di software di maggiore qualità in tempo minore. 1

29 2 UN ESEMPIO DI APPLICAZIONE 1.1 JUnit Lo sviluppo guidato dai test consiste nella tecnica di sviluppare una nuova funzionalità di un programma attraverso la ripetizione continua di un ciclo di produzione molto breve che consiste di due sole fasi: (1) scrittura di un test che il programma, con la nuova funzionalità, deve poter passare, (2) se il test non viene superato con successo, si modifica il codice nella maniera più semplice possibile per far passare il test. Non si può passare ad un nuovo test se non si sono superati tutti i test precedenti. Basando tutto lo sviluppo di software su questi rapidissimi cicli di produzione, è necessario avere a disposizione uno strumento automatico per la realizzazione di questi test. JUnit, lo strumento che andiamo ora a presentare, ha questo scopo. 1.1 JUnit JUnit è un ambiente per scrivere test scritto da Erich Gamma (autore, con altri, dei Design Patterns) e Kent Beck (autore e guru di Extreme Programming). L ambiente è disponibile anche in altri linguaggi (quali C, C++, SmallTalk, etc.) e supporta il programmatore nella definizione di test, nella loro raccolta in suite di test e nella loro esecuzione. JUnit è Open Source Software, rilasciato sotto la licenza IBM Common Public License (versione 0.5). Ha vinto nel 2002 e nel 2003 il premio JavaWorld Editors Choice Awards (ECA) in qualità di migliore strumento per il Performance Monitoring e il Testing in Java. L obiettivo di JUnit è quello di fornire al programmatore un ambiente che permette di sapere in ogni momento e con poco sforzo se il proprio programma ha superato tutti i test oppure no. Questo permette lo sviluppo rapido del software e la possibilità di rilasciare il programma (release) in ogni momento: infatti ad ogni passo dello sviluppo il programma deve passare con successo il 100% dei test definiti. Questo permette di fondere la fase di testing all interno dello sviluppo, cosa particolarmente utile per progetti di dimensione medio-piccole, dove il codice deve essere pronto ad evolvere guidato dalla dinamicità dei requisiti. Le funzionalità di JUnit rappresentano un ambito familiare dove il programmatore riesce a integrare testing e sviluppo in maniera armonica, permettendo di costruire nuovi test basandosi sui vecchi, di integrare insiemi di test sviluppati da altri programmatori e di valutare con estrema semplicità il risultato dei test. 2 Un esempio di applicazione Presentiamo ora un esempio didattico per illustrare il procedimento di sviluppo guidato dai test. L esempio è scritto in Java ed usa JUnit. E stato sviluppato sotto Eclipse, ma altri ambienti (JBuilder ad esempio) offrono simili funzionalità. In ogni modo, la suite di JUnit permette di svincolarsi completamente da qualsiasi ambiente di sviluppo (non che questo sia necessariamente un bene, attenzione!) e di poter essere eseguita lanciando la JVM da linea di comando. Pag. 2

30 2 UN ESEMPIO DI APPLICAZIONE 2.1 Il nostro obiettivo 2.1 Il nostro obiettivo L obiettivo del nostro esempio è quello di scrivere un metodo media() di una classe Calcolatore che deve, semplicemente, calcolare la media dei valori interi passati come parametro in un vettore e restituirla. Il valore restituito è un intero e, come tale, quindi viene soggetto al troncamento della parte decimale. Insomma la media di 1 e 2 sarà 1. Si tratta, evidentemente, di un metodo che ha valenza esclusivamente esemplificativa eppure, anche attraverso l uso della tecnologia JUnit, riserverà qualche sorpresa anche a programmatori di una certa esperienza. Inoltre, mostrerà come alcune caratteristiche di Eclipse (suggerimenti, etc.) possono complementare semplicemente l utilizzo dell Unit Testing. 2.2 La tecnica di JUnit Ricordiamo la filosofia di uso di JUnit: non si deve scrivere una linea di codice senza che sia scritto un test che ne verifichi il corretto funzionamento. Questa posizione (estremista ma, come vedremo, utile) serve a prevenire una pratica sconveniente che viene utilizzata spesso: si sviluppa il software rimandando la fase di testing successivamente. Quando poi, sotto la pressione per la consegna del lavoro, si rende necessario affrettare (oppure eliminare del tutto) qualche fase del lavoro, la prima candidata risulta essere la fase di testing. Questa non è, chiaramente, una scelta intelligente 2 da fare. Forzando il programmatore a scrivere prima un test si ottiene, innanzitutto, a fare sì che il codice sviluppato sia necessariamente testato. Poi, si ottiene anche un effetto virtuoso : il programmatore impara a scrivere nuovo codice creando un test che fallisca sul suo vecchio codice e che, quindi, in un certo senso lo metta in crisi. In questa maniera, il software sviluppato risulta essere particolarmente robusto a malfunzionamenti, in quanto la attitudine (che qualcuno potrebbe definire masochista) del programmatore ha cercato di evidenziare tutte le possibili anomalie durante la fase di sviluppo. Quindi, ricordiamo, la produzione avviene per cicli brevissimi che si compongono delle due fasi seguenti: 1. sviluppo di un test che rappresenta una funzionalità che il nostro codice deve poter offrire; 2. se il codice non viene passato, si propone la più semplice soluzione che possa risolvere il problema. Un commento merita la enfasi sulla semplicità della soluzione da proporre. A volte, sembra che questo suggerimento non abbia molto senso: in un certo senso la soluzione può anche cercare di prevenire problemi futuri. Questo, però, rappresenta esattamente il contrario della filosofia dello sviluppo guidato 2 E stato scritto che questo equivale a spegnere la luce proprio quando la notte si fa più buia. Pag. 3

31 2 UN ESEMPIO DI APPLICAZIONE 2.3 Come usare JUnit con Eclipse dai test che richiede che ogni parte di codice sia testata. Quindi, scrivere soluzioni complesse significherebbe introdurre codice cui non corrisponde un test da superare e quindi, codice non affidabile. 2.3 Come usare JUnit con Eclipse All interno di un progetto Eclipse, si deve innanzitutto aggiungere l uso come una External jar di junit, specificando il file junit.jar (che si trova sotto la directory plugins/org.junit org.junit vxxxx (dove xxxx indica la versione) nella home di Eclipse). Basta modificare le Proprietà del progetto, aggiungendo nel Java Build Path il file indicato, come in figura 1. Figura 1: Come aggiungere sul build path del progetto il file junit.jar. JUnit, dal momento in cui sono stati preparati questi appunti, è arrivato alla versione JUnit 4.0 che usa le nuove caratteristiche di JDK (le annotazioni, ad esempio) per rendere ancora più semplice la realizzazione dei test. In questo esempio, comunque, continuiamo ad usare la versione JUnit (l ultima prima della 4) proprio perché ancora maggiormente diffusa. Le modifiche di JUnit 4.0 consistono essenzialmente nel non dover denominare i metodi di test in modo che inizino con la parola testxxx. Maggiori dettagli sulle modifiche sono disponibili a [5]. Il file junit.jar 4.0 sotto Eclipse si trova nella directory org.junit Il primo passo Iniziamo, secondo il puro stile JUnit, con lo scrivere il nostro codice iniziando dal test. Quindi, dovendo scrivere un metodo che calcoli la media di un vettore di interi, si deve partire dal primo test. Creiamo, quindi una test unit (come classe JUnit) che faccia un test iniziale. La prima stesura del file (che chiameremo CalcolatoreTest.java) sarà seguente: Pag. 4

32 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo 1 import junit.framework.testcase; 2 public class CalcolatoreTest extends TestCase { 3 Calcolatore c; 4 public void setup(){ 5 c = new Calcolatore(); 6 } 7 public void testiniziale(){ 8 int[] valori = new int[]{0}; 9 assertequals(0, c.media(valori)); 10 } 11 } Come si può notare alla linea 2, la classe CalcolatoreTest eredita dalla classe junit.framework.testcase.testcase. Dopo avere dichiarato come campo della classe una variabile c oggetto istanza della classe Calcolatore (linea 3), definiamo, innanzitutto, un metodo setup() (linee 4-6) che ha lo scopo di approntare gli oggetti che servono per ogni test. Ricordiamo che il metodo setup() viene chiamato prima della esecuzione di ogni metodo di questa classe il cui nome inizia per test. In questo esempio non abbiamo la necessità di utilizzare il metodo teardown() che viene chiamato alla fine della esecuzione di ogni singolo test. Fedeli alla filosofia del Test-Driven Development, cerchiamo di scrivere il primo test quando ancora non abbiamo scritto neanche una riga della classe Calcolatore. La cosa può sembrare paradossale, ma una volta abituati e, specialmente, una volta osservato i vantaggi che questa tecnica porta, diventerà una maniera molto naturale di procedere. Per prima cosa vogliamo che il nostro metodo calcoli correttamente la media Test # 1. La media di 0 è 0. di 0. Quindi, il test che vogliamo scrivere, che chiamiamo, opportunamente, testiniziale() è contenuto alle linee La prima cosa che vogliamo controllare è che questo metodo calcoli correttamente la media di un solo valore, 0, che viene inserito in un vettore di interi chiamato valori dimensionato ad un solo elemento. Questo viene effettuato controllando (linea 9) con assertequals che il risultato restituito dal metodo media() passando valori come parametro sia uguale a 0. A questo punto possiamo passare ad effettuare il primo di una lunga se- Rosso rie di test a cui sottoporremo la nostra classe Calcolatore. In effetti, questo primo test risulta davvero estremamente sfortunato: non riusciamo neanche a compilare la classe CalcolatoreTest per il semplicissimo motivo che la classe Calcolatore (cioè la classe che è da testare) non esiste ancora! Quindi, dobbiamo, come prima cosa creare questa classe con il metodo richiesto media(). Se ci affidiamo ai suggerimenti di Eclipse (quelli con la lampadina a fianco delle linee degli errori/warning) possiamo ottenere con poco sforzo la creazione del metodo richiesto (vedi Fig. 2. Pag. 5

33 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo Figura 2: Come si usano i suggerimenti di Eclipse per aggiungere facilmente le classi ed i metodi della classe Calcolatore. Attenzione! Il suggerimento di creare il metodo media() che restituisce un Object è sbagliato... si deve modificare a mano: ah, che fatica! Ecco il risultato: 1 public class Calcolatore { 2 public int media(int[] valori) { 3 return 0; 4 } 5 } In effetti, avere scritto questa versione (assolutamente minimalista) della classe Calcolatore, risponde alla precisa indicazione di semplicità che viene dettata dalle prassi della cosiddetta Extreme Programming. In sintesi, il principio richiede al programmatore di scrivere la soluzione più semplice per poter far passare il test. Non si richiede (anzi viene sconsigliato) il cercare di progettare Pag. 6

34 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo ipotizzando i test futuri a cui il nostro oggetto verrà sottoposto. In effetti, dopo aver scritto queste poche righe di codice possiamo lanciare CalcolatoreTest ed il nostro test testiniziale() viene passato: il nostro metodo media() calcola la media di 0. Questo viene visualizzato in una view a parte mostrata insieme al Package Explorer sulla sinistra, come in Fig. 3. Verde Figura 3: Il primo lancio di un test: la view a sinistra mostra il risultato con gli eventuali errori Quando abbiamo finito di festeggiare, possiamo passare al nostro prossimo test. Diciamo che vorremmo che il nostro metodo calcoli correttamente la Test # 2. media di 0 ma anche la media di 1, 2,..., diciamo almeno fino a 9. Quindi possiamo passare a scrivere il prossimo test che chiamiamo testognivalore(). Ricordiamo che ad ogni passo verranno rieseguiti tutti i test contenuti nel TestCase CalcolatoreTest, quindi alla prossima esecuzione verranno eseguiti sia testiniziale() che testognivalore(), e così via per ogni test che aggiungeremo. Ecco il codice di testognivalore(): La media di 1 è 1, quella di 2 è 2, etc. fino a 9. 1 public void testognivalore() { Pag. 7

35 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo 2 int[] valori = new int[1]; 3 String message; 4 for (int i=0; i<10; i++) 5 { 6 valori[0]=i; 7 message = "Media di "+i; 8 assertequals(message,i, c.media(valori)); 9 } 10 } Come possiamo notare, il test controlla che il valore sia corretto alla linea 8. Lanciamo questo test e, non senza stupore, riscontriamo che il test fallisce quando proviamo a calcolare la media di 1. Il risultato viene mostrato dalla barra rossa (scura, granata...) nella view di JUnit testing come in Fig. 4 Rosso Figura 4: Il primo lancio con un errore di un test: la view a sinistra mostra il risultato errato (barra rossa) con gli errori rappresentati dalla eccezione lanciata quando si cerca di calcolare la media di 1, che non è 0. Pag. 8

36 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo Bene. Proviamo a fare passare questo test. Ricordiamo l obbligo di mantenere la semplicità nelle modifiche. Quindi la nostra modifica consiste nello scrivere il metodo media() in modo che restituisca come media il primo valore del vettore così: 1 public int media(int[] valori) { 2 return valori[0]; 3 } Lanciamo il test e notiamo che viene superato. Verde Proviamo a controllare se il nostro metodo calcola la media correttamente Test # 2. anche se passiamo un valore negativo. Quindi quello che vogliamo controllare è effettuato dal questo metodo che aggiungiamo a CalcolatoreTest. La media di -1 è - 1, quella di -2 è -2, etc. fino a public void testvalorenegativo() { 2 int[] valori = new int[1]; 3 String message; 4 for (int i=-9; i<0; i++) 5 { 6 valori[0]=i; 7 message = "Media di "+i; 8 assertequals(message,i, c.media(valori)); 9 } 10 } Eseguiamo il test ed otteniamo che va tutto bene. Questo in generale, non è un bene... Effettivamente, il test che abbiamo scritto sembra inutile (in questo momento) e dobbiamo passare ad un passo successivo, insomma abbiamo preso coraggio ed è ora di passare oltre. Verde Adesso, coraggiosamente cerchiamo di controllare se il metodo media() Test # 3. calcola la media correttamente quando passiamo due valori uguali. Questo controllo viene effettuato da questo test: La media di 1 e 1 è 1, quella di 2 e 2 è 2, etc. fino a 9 e 9. 1 public void testognicoppiaconstessovalore() { 2 int[] valori = new int[2]; 3 String message; 4 for (int i=0; i<10; i++) 5 { 6 valori[0]=i; 7 valori[1]=i; 8 message = "Media di "+i+" e "+i; 9 assertequals(message,i, c.media(valori)); Pag. 9

37 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo 10 } 11 } Effettuiamo il test e (ancora una volta!) otteniamo che il test funziona. Verde Dobbiamo essere ancora più coraggiosi, proviamo a fare la media di coppie Test # 4. di valori diversi! Quindi scriviamo in CalcolatoreTest il metodo seguente che controlla se media() calcola correttamente la media tra 7 e 9: La media tra 7 e 9 è 8. 1 public void testduevalori(){ 2 int[] valori = new int[]{7,9}; 3 assertequals(8, c.media(valori)); 4 } Lanciamo il test e, finalmente, notiamo che il nostro test fallisce. Finalmente possiamo mettere mano al codice del nostro metodo. La soluzione sembra semplice, no? In effetti, basta restituire il valore della media calcolata sui due interi, semplice. E quello che facciamo: Rosso 1 public int media(int[] valori) { 2 return (valori[0]+ valori[1])/2; 3 } Fatto? Proviamo ad eseguire il test e, con sorpresa, notiamo che non va a buon fine! Infatti questa volta il test che fallisce è uno dei precedenti: la modifica che abbiamo fatto, mentre ha fatto superare l ultimo test, ha però reso non più corretto il testiniziale lanciando la eccezione ArrayIndexOutOfBoundsException. Infatti, basta notare che, quando passiamo un vettore di un solo elemento il risultato dipende solo dal primo, mentre nel caso in cui passiamo due valori (diversi) il risultato dipende da entrambi. In fig. 5 si nota come anche il test testognivalore fallisce, per lo stesso motivo. Gli errori provengono dai test precedenti: l uso di JUnit ci permette di far sì che il nostro programma faccia solo passi in avanti e non degli apparenti miglioramenti che però creino problemi in test precedenti. Quindi (finalmente!) comprendiamo che è importante utilizzare all interno del codice la lunghezza del vettore passato come parametro a media(). Un poco di riflessione (ma davvero poco) ci convince che dobbiamo necessariamente calcolare la media in funzione del numero di elementi passati nel vettore, scrivendo quindi il codice seguente: Rosso 1 public int media(int[] valori) { 2 int sommavalori=0; 3 for (int i=0; i< valori.length ; i++) { 4 sommavalori += valori[i]; 5 } Pag. 10

38 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo Figura 5: Gli errori generati dalla modifica effettuata vengono dai test precedenti 6 return sommavalori/valori.length; 7 } Ed adesso il nostro metodo funziona correttamente su ciascuno dei test che abbiamo sviluppato. Verde Giusto per essere sicuri, facciamo in modo di controllare che la media di Test # 5. tutte le possibili combinazioni di valori tra 0 e 9 sia corretta. Scriviamo quindi questo metodo: La media di tutte le coppie i e j, tra 0 e 9 è corretta. 1 public void testognicoppia() { 2 int[] valori = new int[2]; 3 String message; 4 for (int i=0; i<10; i++) 5 for (int j=0; j<10; j++) { 6 valori[0]=i; Pag. 11

39 2 UN ESEMPIO DI APPLICAZIONE 2.4 Il primo passo 7 valori[1]=j; 8 message = "Media di "+i+" e "+j; 9 assertequals(message,(i+j)/2, c.media(valori)); 10 } 11 } Funziona! In effetti, adesso possiamo eseguire tutti i nostri test ed ottenere finalmente che tutti funzionano, come si vede in Fig.6. Verde Figura 6: I 5 test superati con successo dal metodo media() così come descritto finora. Questa stesura del metodo della media rappresenta quella che probabilmente qualsiasi programmatore avrebbe realizzato in partenza. La cosa, adesso, si fa interessante. Come possiamo cercare di costruire un test che questo metodo non riesce a superare? Pag. 12

40 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice 2.5 Miglioriamo il codice E venuto il momento di testare cosa succede se forniamo al metodo dei dati in maniera non corretta. Ad esempio, un primo controllo che si dovrebbe fare è Test # 6. quello di controllare che il metodo media() quando viene passata una variabile con valore null restituisca una eccezione IllegalArgumentException. Come al solito, questo viene effettuato scrivendo prima il test, facendolo fallire e poi modificando il codice per far passare il test (e tutti i precedenti). Creiamo, quindi, questo metodo: Con vettore null, lancia una eccezione. 1 public void testarraynull() { 2 int[] valori = null ; 3 try { 4 int media = c.media(valori); 5 fail ("Permette un array null!"); 6 } catch (IllegalArgumentException e) { 7 // non colta 8 } 9 } E da notare che alla linea 5 non si fa altro che chiedere al metodo di calcolare la media di un vettore null. Non si controlla il risultato: piuttosto, se la chiamata a media() non genera una eccezione IllegalArgumentException(che viene colta dalla catch della linea 6) allora lanciamo una eccezione con il metodo fail(). Eseguiamo il test e vediamo che effettivamente questo fallisce. In effetti, fallisce perché viene generata una eccezione NullPointerException nella riga 3 della ultima stesura del metodo media(), quando si cerca di accedere al campo length di un riferimento null (vedi la Fig. 7 Come possiamo modificare il codice di media()? Inserendo un controllo (a inizio metodo) che il parametro passato non sia nullo. Ecco fatto: Rosso 1 public int media(int[] valori) { 2 int sommavalori=0; 3 if (valori == null) 4 throw new IllegalArgumentException(); 5 for (int i=0; i< valori.length ; i++) { 6 sommavalori += valori[i]; 7 } 8 return sommavalori/valori.length; 9 } Tutto a posto! Adesso lanciamo i test e vanno a buon fine tutti. Verde Continuiamo a cercare di mettere in crisi il metodo media(). Una situazione Test # 7. abbastanza intricata non è stata ancora controllata. Infatti, se passiamo al Con vettore di lunghezza 0, lancia una eccezione. Pag. 13

41 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice Figura 7: Il fallimento del test sull array null passato come parametro: il fatto che venga lanciata una eccezione NullPointerException rappresenta l errore... avrebbe dovuto lanciare la eccezione IllegalArgumentException. metodo media() un vettore di interi che ha lunghezza 0, questo potrebbe creare dei problemi. Vediamo: 1 public void testarrayvuoto () { 2 int[] valori = new int[0]; 3 try { 4 int media = c.media(valori); 5 fail ("Permette un array di zero elementi!"); 6 } catch (IllegalArgumentException e) { 7 // non colta 8 } Effettivamente ci siamo riusciti! Adesso il nostro metodo media() dà un Rosso Pag. 14

42 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice errore dovuto alla divisione per zero che si effettua alla fine nella linea 8, essendo valori.length pari a 0, lanciando una eccezione java.lang.arithmeticexception (vedi Fig. 8. Figura 8: Il fallimento del test sull array di dimensione 0. Modifichiamo il codice di media() per risolvere questo problema. Supponiamo, ora, che il programmatore compia un errore dovuto alla distrazione cioè che inserisca nella funzione media un controllo che nella condizione dell if contenga l AND logico delle condizioni (valori == null) e (valori.length == 0) invece che l OR logico, come sarebbe corretto. Un errore banale, ma che può capitare. Quindi modifichiamo il codice come segue: 1 public int media(int[] valori) { 2 int sommavalori=0; 3 if (valori == null && valori.length==0) 4 throw new IllegalArgumentException(); 5 for (int i=0; i< valori.length ; i++) { 6 sommavalori += valori[i]; Pag. 15

43 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice 7 } 8 return sommavalori/valori.length; 9 } A questo punto, il programmatore (che ha compiuto l errore) potrebbe ritenere di aver risolto il problema e passare a fasi successive (magari anche ad altro codice). Invece, la metodologia dello sviluppo guidato dai test, obbliga a controllare che ogni linea di codice superi il 100% dei test elaborati. Quindi, viene lanciato il test che, contrariamente alle aspettative del programmatore, non viene passato. Come si vede in Fig.9, addirittura i test falliti diventano due, in quanto non solamente non riconosce il parametro di lunghezza zero, ma non lancia neanche più l eccezione sull array null che prima, invece, funzionava. Rosso Figura 9: Il fallimento dei due test sull array di dimensione 0 e sull array null, dovuti all errore evidenziato sul codice. Quindi il programmatore può scoprire l errore e scrive correttamente il codice come segue: Pag. 16

44 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice 1 public int media(int[] valori) { 2 int sommavalori=0; 3 if (valori == null valori.length==0) 4 throw new IllegalArgumentException(); 5 for (int i=0; i< valori.length ; i++) { 6 sommavalori += valori[i]; 7 } 8 return sommavalori/valori.length; 9 } Si può controllare il programma e verificare che ora tutti i test (compreso l ultimo) vengono passati con successo. Facciamo un attimo di pausa e commentiamo sulla attitudine di cercare di mettere in crisi il proprio codice per scoprirne debolezze e correggerle. L ultimo test rappresenta un classico esempio di come questa maniera di programmare produca codice più robusto ed affidabile. Un metodo media() come quello che abbiamo elaborato finora gestisce anche casi limite (come, ad esempio, il vettore non nullo ma di zero elementi) che normalmente non ci saremmo posti come problema (da programmatore) se il gioco che stiamo giocando non comportasse l obbligo di cercare di far fallire il nostro stesso programma per correggerne gli errori Non solo, proprio l ultimo esempio ha dimostrato come la abitudine a dover passare un test per ogni linea di codice scritta permette di far scoprire al programmatore distratto alcuni errori che magari sarebbero passati inosservati 3 come quello dell utilizzo di un AND logico al posto di un OR logico. Verde Adesso, immaginiamo un altro tipo di casi limite, ad esempio, quando i valori Test # 8. passati all interno del vettore valori risultano essere molto grandi. Quindi, controlliamo che il metodo media() calcoli correttamente la media di un singolo valore che è pari al massimo valore memorizzabile in un intero, che è definito come una costante nella classe wrapper per gli interi: Integer.MAX VALUE. Scriviamo quindi il metodo seguente: La media di MAX VALUE è MAX VALUE. 1 public void testmaxvalueintero() { 2 int[] valori = new int[1]; 3 String message; 4 valori[0]=integer.max_value; 5 message = "Media di "+valori[0]; 6 assertequals(message,integer.max_value, c.media(valori)); 7 } Proviamo ad eseguirlo... e funziona. Dobbiamo trovare qualche cosa di meglio. Verde 3 La legge di Murphy garantisce che gli errori si sarebbero evidenziati nella peggiore delle situazioni in maniera, cioé, da recare simultaneamente e nella maniera più evidente possibile Pag. 17

45 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice Proviamo a scrivere un test simile ma questa volta che fallisca. Ovviamente, il problema si presenterà se andiamo a calcolare la media di due MAX VALUE. Scriviamo quindi il seguente metodo, prevedendo che in caso di overflow venga Test # 9. generata un eccezione ArithmeticException: 1 public void testduemaxvalueintero() { 2 int[] valori = new int[2]; 3 String message; 4 try { 5 valori[0]=integer.max_value; 6 valori[1]=integer.max_value; 7 message = "Media di "+valori[0]+" e "+valori[1]; 8 assertequals(message,integer.max_value, c.media(valori)); 9 fail ("Non riconosce l overflow!"); 10 } catch (ArithmeticException e) { 11 // non colta 12 } 13 } La media di 2 valori MAX VALUE è MAX VALUE. Evviva! Il test fallisce! In effetti, fallisce per prima cosa perché non riconosce che il risultato è corretto: infatti la media che viene restituita è (stranamente...) -1 (vedi la Fig. 10). In effetti, si deve trattare il problema dell overflow: stiamo sommando dei valori interi e potremmo oltrepassare la capacità di memorizzazione di un intero in Java. Due sono i passi che dobbiamo fare: (1) Come si fa a controllare se abbiamo generato un overflow? (2) cosa dobbiamo fare in questo caso? La risposta alla seconda domanda è semplice: si deve lanciare una eccezione di tipo ArithmeticException. La risposta alla prima domanda invece si nasconde nei meandri dei ricordi degli studi effettuati al primo anno del corso di Laurea in Informatica. Infatti, durante il corso di Architettura, si impara come controllare l overflow di operazioni in complemento a due: quando si sommano due positivi il risultato deve essere positivo, altrimenti vuol dire che abbiamo generato un overflow. A questo punto la maniera per modificare il metodo media() è il seguente: Rosso 1 public int media(int[] valori) { 2 int sommavalori=0; 3 int appoggiosomma; 4 if (valori == null valori.length == 0) 5 throw new IllegalArgumentException(); 6 for (int i=0; i< valori.length ; i++) { 7 appoggiosomma = sommavalori; 8 sommavalori += valori[i]; il massimo danno al cliente, allo sviluppatore ed al manager del progetto. Pag. 18

46 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice Figura 10: Il fallimento del test con due interi di grandi dimensioni. 9 if (appoggiosomma > 0 && valori[i]> 0 && sommavalori < 0) 10 throw new ArithmeticException(); 11 } 12 return sommavalori/valori.length; 13 } Quello che è stato fatto è semplicemente l inserimento di una nuova variabile intera appoggiosomma che mantiene (linea 7) il valore della variabile sommavalori prima che questa venga modificata (nella linea 8). A questo punto si può controllare se la somma del vecchio valore e del valore che stiamo sommando (valori[i]) ha generato un valore negativo (linea 9). In questo caso lanciamo una eccezione (linea 10). Controlliamo se funziona tutto. Lanciamo il test ed otteniamo che non ci sono errori. Ok, bene. Abbiamo capito in che direzione andare. Dovremmo testare cosa succede anche con MIN VALUE. In questo momento dobbiamo ricordarci di Verde Pag. 19

47 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice applicare (alla lettera) i dettami del Test-driven Development: si deve modificare il programma nella maniera più semplice per poter passare il test che aveva fallito in precedenza. Quindi, prima di fare qualsiasi modifica che permetta di gestire overflow negativi dobbiamo scrivere un test e farlo fallire. Quindi, proviamo Test # 10. a vedere se riusciamo a calcolare correttamente la media di due valori negativi molto piccoli (MIN VALUE) con questo metodo: La media di 2 valori MIN VALUE è MIN VALUE. 1 public void testdueminvalueintero() { 2 int[] valori = new int[2]; 3 String message; 4 try { 5 valori[0]=integer.min_value; 6 valori[1]=integer.min_value; 7 message = "Media di "+valori[0]+" e "+valori[1]; 8 assertequals(message,integer.min_value/2, c.media(valori)); 9 fail ("Non riconosce l overflow negativo!"); 10 } catch (ArithmeticException e) { 11 // non colta 12 } 13 } Evviva! Non funziona. Adesso possiamo modificare il programma. E la cosa sembra facile: abbiamo appena tratta il caso dell overflow positivo, basta invertire le condizioni e tratteremo l overflow negativo. Vediamo cosa otteniamo. Rosso 1 public int media(int[] valori) { 2 int sommavalori=0; 3 int appoggiosomma; 4 if (valori == null valori.length == 0) 5 throw new IllegalArgumentException(); 6 for (int i=0; i< valori.length ; i++) { 7 appoggiosomma = sommavalori; 8 sommavalori += valori[i]; 9 if (appoggiosomma > 0 && valori[i]> 0 && sommavalori < 0) 10 throw new ArithmeticException(); 11 if (appoggiosomma < 0 && valori[i]< 0 && sommavalori > 0) 12 throw new ArithmeticException(); 13 } 14 return sommavalori/valori.length; 15 } Bene, sembra fatta. Eseguiamo il test... e non funziona! Perché? Abbiamo semplicemente invertito le condizioni per l overflow positivo? Perché non va? Guardate la situazione mostrata in Fig. 11: l errore mostrato dalla view di Rosso Pag. 20

48 2 UN ESEMPIO DI APPLICAZIONE 2.5 Miglioriamo il codice JUnit è che il risultato dovrebbe essere MIN INT mentre invece è 0. Questo è strano... Figura 11: Il fallimento del test con due interi negativi MIN INT. Un pochino di osservazione vi farà notare che le condizioni di overflow non sono speculari: mentre basta controllare che la somma di due positivi dia un negativo per controllare l overflow positivo, per controllare l overflow negativo si deve controllare anche che il risultato non sia zero! Infatti la somma di due valori MIN VALUE dà come risultato proprio zero! Quindi dobbiamo modificare la linea 11 in modo da controllare anche se il risultato della somma di due negativi dà zero, come segue: 1 public int media(int[] valori) { 2 int sommavalori=0; 3 int appoggiosomma; 4 if (valori == null valori.length == 0) 5 throw new IllegalArgumentException(); 6 for (int i=0; i< valori.length ; i++) { Pag. 21

49 3 CONCLUSIONI 7 appoggiosomma = sommavalori; 8 sommavalori += valori[i]; 9 if (appoggiosomma > 0 && valori[i]> 0 && sommavalori < 0) 10 throw new ArithmeticException(); 11 if (appoggiosomma < 0 && valori[i]< 0 && sommavalori >= 0) 12 throw new ArithmeticException(); 13 } 14 return sommavalori/valori.length; 15 } Bene, adesso funziona tutto. Verde 3 Conclusioni 3.1 Alcuni commenti La tecnologia relativa a JUnit rappresenta uno strumento molto utile per il supporto al programmatore. Una volta abituatisi allo stile di programmazione (che richiede di partire, in maniera apparentemente irrazionale, dalla scrittura di test per arrivare (solo successivamente) alla scrittura del codice) l aiuto che viene fornito al programmatore risulta notevole. Con questo semplice esempio abbiamo voluto enfatizzare alcuni degli aspetti positivi della tecnica programmazione guidata dai test. In particolare, ritengo opportuno sottolineare come alcune situazioni potenzialmente pericolose sono state evitate proprio con la enfasi sulla attività di test che deve precedere quella di codifica. Ad esempio, possiamo citare il passo del Test n. 10 che verificava la corretta segnalazione di overflow in caso di somma di due valori MIN VALUE. La asimmetria nella condizione di test per l overflow positivo e quella per l overflow negativa sarebbe potuta probabilmente sfuggire anche a programmatori esperti. L obbligo di fornire prima un test specifico, che deve essere passato con successo, ha forzato il programmatore a scrivere un test apparentemente banale e inutile (vale a dire, che doveva sicuramente essere passato) che ha rivelato invece un problema nel codice la cui soluzione ha permesso di creare del codice di maggiore qualità (robusto ed affidabile). Una situazione di errore diversa, ma egualmente pericolosa, si è verificata nel il Test n. 7 che ha permesso di scoprire immediatamente una distrazione del programmatore. Riconoscere immediatamente un errore così banale ha dei notevoli vantaggi pratici ed economici: in quel momento, e trattando quei programmi, il programmatore ha immediatamente riconosciuto e potuto correggere l errore. Immaginate, invece, il tempo che avrebbe impiegato se, magari dopo due mesi, un tester (o peggio, il cliente!) avesse chiamato per dire che A un certo punto viene generata una eccezione... e non funziona più nulla!. Il programmatore potrebbe impiegare giorni interi per trovare questo semplice errore. Un altro aspetto particolarmente interessante è il fatto che il nostro progetto Pag. 22

50 RIFERIMENTI BIBLIOGRAFICI 3.2 Note bibliografiche software compie solamente passi avanti in quanto ad ogni fase di test effettuiamo tutti i test contenuti nella test unit. Questo ha permesso di evidenziare errori che possono essere stati inseriti successivamente, modificando parti del codice apparentemente non correlate. Dopo aver terminato la descrizione delle meraviglie di questo framework, una parola deve essere spesa per avvertire di alcune limitazioni della tecnologia JUnit. Innanzitutto, si deve assolutamente evitare di considerare codice prodotto da questa tecnica come error free. Diciamo che la tecnologia JUnit facilita la vita di chi deve scrivere codice attraverso una attività di test continuo ma che non forza nessuno a scrivere dei test significativi. Insomma, JUnit è uno strumento efficace, ma semplicemente il fatto di usarlo non garantisce la qualità del nostro software: ci vuole anche molta inventiva, esperienza e capacità per usarlo al meglio. Poi, si dovrebbe tenere presente le limitazioni di uso di JUnit all interno di applicazioni. Ad esempio, mentre sembra immediato l uso per le parti della applicazione che compongono la business logic, è più difficile immaginarne l uso per la componente di visualizzazione. Anche se qualche sistema di testing automatico per interfacce su palmari esiste, un sistema così completo come JUnit per testare la qualità di una interfaccia sembra complicato da ottenere. 3.2 Note bibliografiche Molte informazioni su Extreme Programming possono essere trovate su alcuni siti dedicati a questa metodologia di programmazione [1, 2]. Materiale ed informazioni su JUnit si trovano, invece, sul sito ufficiale dedicato a JUnit [3], e nella documentazione che si trova nel package che viene scaricato (nella directory doc). Infine, interessante è il materiale contenuto nella JUnit Frequently Asked Questions [4]. L esempio di uso della media è stato ispirato, per la parte iniziale, da un più semplice esempio mostrato in seminari/2002/webbit/tdd/main.html. Versioni Versione (13/1/2004): prima stesura. Versione 1.1 (9/4/2006): aggiornamento a Eclipse 3.1. Riferimenti bibliografici [1] [2] [3] JUnit Official Home Page [4] JUnit Frequently Asked Questions Pag. 23

51 RIFERIMENTI BIBLIOGRAFICI RIFERIMENTI BIBLIOGRAFICI [5] Junit 4.0 in 10 minutes, di Gunjan Doshi instrumentalservices.com/index.php?option=com_content&task= view&id=45&itemid=52 Pag. 24

52 Selenium Testing of Web-based applications

53 Selenium Testing of Web-based applications Ingegneria del Software II - A.A. 2007/2008 Software Engineering Lab Dipartimento di Matematica e Informatica Università degli studi di Salerno

54 Agenda What is Selenium? Writing a Selenium test case The Selenium IDE Live demo Usage scenarios Conclusion

55 What is Selenium? Selenium is a test tool for web applications It runs directly in a browser, just as real users do It works with Internet Explorer, Mozilla and Firefox on Windows, Linux, and Macintosh Selenium uses JavaScript and Iframes to... embed a test automation engine in a browser It should work with any JavaScript-enabled browser Any language! Java,.NET, Perl, Python and Ruby

56 Which kinds of tests? Browser compatibility testing Verify if the system works correctly on different browsers and operating systems The same script can run on any Selenium System functional testing Create regression tests to verify application functionality and user acceptance

57 Where did Selenium come from? Selenium was developed by ThoughtWorks Open-source software and currently under development ThoughtWorks is a leader in Agile development methods Selenium is designed specifically for the acceptance testing requirements of Agile teams There are hopes for it to... become the multi-language, multi-platform defacto standard replacement for the likes of WinRunner in the web application space

58 How to write a test case A Selenium test case is a set of command (test table) Each row specifies... command: the command to execute target: the element the command refers to value: the expected value of an input field Examples open - - type - document.login.login - pippo

59 Type of commands Actions Manipulate the state of the application (e.g., open, click, etc) If an Action fails, the execution of the test stopped AndWait suffix, e.g., clickandwait Accessors Examine the state of the application and store the results Assertions Verify the state of the application See Selenium Reference for more details

60 Assertions assert When an assert fails, the test is aborted verify When a verify fails, the test will continue execution, logging the failure Usually used after an assert waitfor Waits for some condition to become true true: the test immediately continues the execution false: the test is aborted

61 Element locators and Patterns Element locators: identify an element a command refer to target attribute elementid of document.forms[0].element Pattern: specify the expected value of an input field Regular-expressions See Selenium Reference for more details

62 Example: Login (Java) import com.thoughtworks.selenium.*; import java.util.regex.pattern; public class NewTest extends SeleneseTestCase { public void testnew() throws Exception { selenium.open("http://localhost:8080/selenium/index.jsp"); selenium.type("document.login.login", "pippo"); selenium.type("document.login.password", "pluto"); assertequals("http://localhost:8080/selenium/home.jsp", selenium.getlocation()); } }

63 Tedious task! Writing a test case is tedious... we have to write code... approach similar to JUnit No problem... The Selenium IDE

64 Selenium IDE An integrated development environment for Selenium tests It is implemented as a Firefox extension Allows to record, edit, and debug tests Easy and quick recording and playing back tests in the actual environment that they will run A complete IDE with recording capability, but also editing of scripts by hand Autocomplete support and the ability to move commands around quickly Ideal environment for creating Selenium tests no matter what style of tests you prefer

65 Selenium IDE: features Easy record and playback Intelligent field selection will use IDs, names, or XPath as needed Autocomplete for all common Selenium commands Walk through tests Debug and set breakpoints Save tests as HTML, Ruby scripts, or any other format

66 Test suite Create single test cases with Selenium IDE Save them as HTML Create a HTML file test suite Table of test cases Run the test suite chrome://selenium-ide/content/selenium/testrunner.html? baseurl=http://localhost&test=file:///users/roliveto/desktop/tests/ TestSuite.html&auto=true

67 Conclusion Selenium Support for regression testing of web application Automatic writing of test cases (Selenium IDE) Considering the simplicity of it, it is almost surprising that no one has thought of doing this previously. The framework is simple and the code is neat and very maintainable. Sometimes it takes a work of genius to find the uncomplicated solution to a potentially complicated problem. - Antony Marcano SELENIUM: FORUM:

68 IS2 -> Capitolo 3 CAPITOLO 3: VERIFICATION AND VALIDATION VERIFICATION & VALIDATION Il testing è la fase in cui si va testare e ad agire sul miglioramento del codice. Serve ad analizzare e valutare la correttezza di una implementazione con riferimento alle caratteristiche definite nei requisiti, nelle specifiche e nel progetto. Iniziato negli anni 90, era rivolto al raggiungimento della qualità. Il testing è costituito da 2 operazioni: verifica e convalida. La prima può essere fatta su una cosa informale, se il sistema si comporta in modo inerente ai casi d uso (ad esempio la usabilità, non può essere misurata in maniera quantitativa); nella maggior parte dei casi si effettuano dei test sull usabilità. Verifica risponde alla domanda: stiamo costruendo il prodotto nel modo giusto? La convalida invece risponde alla domanda: stiamo realizzando il prodotto giusto, come l utente lo vuole?. Secondo questa definizione, il ruolo della verifica è controllare che il software sia conforme alle sue specifiche: deve soddisfare i requisiti funzionali e non funzionali specificati. La convalida invece è un processo più generale, che va oltre la verifica: il suo scopo è assicurarsi che il sistema software rispetti le attese del cliente. La convalida è necessaria perché serve a verificare che ciò che è stato realizzato sia consono a quello che è stato richiesto dall utente (si va a convalidare la specifica dei requisiti). Nel caso si sbagli la fase di convalida, questo verrà scoperto solo nel testing di accettazione. Alcune caratteristiche sono convalidabili, altre sono verificabili. Esempi: Verificabili: correttezza, affidabilità, sicurezza, robustezza, interoperabilità Convalidabili: usabilità Efficienza?: la risposta entro un tempo definito è verificabile, altrimenti l efficienza è convalidabile. Progettare verifiche è molto più facile che progettare convalide. All interno del processo di verifica e convalida, ci sono due approcci complementari al controllo e all analisi del sistema. Software inspection: analisi statica delle rappresentazioni del sistema per scoprire problemi (static verification); l analisi del codice e dei documenti può essere supportata da tool. È basata su tecniche di analisi statica del software senza ricorso alla esecuzione del codice; è un processo di valutazione di un sistema o di un suo componente basato sulla sua forma, struttura, contenuto, ecc. Le tecniche usate sono: ispezione, tecniche tipo compilatore. 26

69 IS2 -> Capitolo 3 Software testing: esecuzione o osservazione del comportamento del prodotto (dynamic verification); il sistema è eseguito con dati di test. È fondata sulla analisi dinamica del codice associato al software e quindi sulla esecuzione dello stesso attraverso dati di ingresso. Viene effettuato attraverso la selezione di casi di test e dati associati, strumentazione del codice e monitoraggio, necessità di un oracolo. La figura mostra che ispezioni e test giocano ruoli complementari nel processo software. La freccia indica gli stadi del processo in cui le tecniche possono essere utilizzate: si possono usare le ispezioni in tutti gli stadi del processo software, a partire dai requisisti ogni rappresentazione leggibile del software può essere ispezionata. Un vantaggio dello sviluppo incrementale è la disponibilità di una versione testabile del sistema nei primi stadi del processo di sviluppo, in modo da testare le funzionalità mentre vengono aggiunte al sistema senza dover effettuare l implementazione completa. Le tecniche statiche possono però controllare solo la corrispondenza tra un programma e le sue specifiche (verifica), non possono dimostrare che il suo funzionamento è efficiente. Per quanto le ispezioni del software siano ora molto utilizzate, il test del programma sarà sempre la principale tecnica di verifica e convalida del software. Il test consiste nell eseguire il programma utilizzando dati simili a quelli realmente elaborati; si scoprono difetti o inadeguatezza del programma analizzando i suoi output e cercando anomalie. I processi V&V e di debugging in genere sono intrecciati: quando si trovano errori durante il testing, si deve modificare il programma per correggerli. Il test e il debugging hanno però obiettivi diversi: Il debugging riguarda l individuazione e l eliminazione dei difetti. Debugging implica formulare ipotesi riguardo il comportamento del programma e quindi verificare queste ipotesi per localizzare gli errori. Il debugging costa molto anche perché se effettuo una modifica, devo effettuare test di regressione in quanto devo conoscere cosa è stato apportato e dove. I processi di verifica e convalida servono per stabilire l esistenza di difetti in un sistema software. Dopo aver scoperto un errore nel programma, lo si deve corregere e si convalida di nuovo il sistema: questo può richiedere una nuova ispezione del programma o l esecuzione dei test di regressione, cioè eseguire un altra volta i test esistenti. I test di regressione vengono utilizzati per verficare che le modifche effettuate al programma non abbiano introdotto nuovi errori. La fase di debugging è iniziata nel Negli anni 70 si hanno avuto i primi seri tentativi di fornire dei fondamenti teorici ed approcci sistematici. L obiettivo del testing è quello di dimostrare la correttezza dei programmi. Quindi un test perfetto si ha quando il programma non contiene errori. Negli anni 80 sono state definite nuove descrizioni del testing: è visto come un processo complesso, dai costi estremamente elevati. Il testing è il processo di esecuzione del software con l obiettivo di trovare malfunzionamenti. 27

70 IS2 -> Capitolo 3 Compaiono i primi standard di come deve essere effettuata una fase di testing. Un test che non rivela malfunzionamenti è un testo fallito. Il testing non può dimostrare l assenza di difetti, ma può solo dimostrare la presenza di difetti. Negli anni 90 si ha il consolidamento del rapporto fra testing e qualità con una più profonda conoscenza e miglioramento dei processi di testing. Il testing è pianificazione, progettazione, costruzione, manutenzione e realizzazione di test ed ambienti di test. STANDARD IEEE IEEE : in questo documento ci sono le operazioni di un sistema software o di alcune componenti sotto delle specifiche condizioni osservando e registrando i risultati e creando una valutazione degli aspetti del sistema o della componente. IEEE : in questo documento viene descritto il processo di analisi dei componenti di un sistema software per eliminare le differenze tra le condizioni esistenti e quelle richieste e per valutare le feature degli elementi del software. Una failure (fallimento) è un evento osservabile percepito dall utente con una mancanza di prestazione di un servizio atteso. Un fault (difetto) è la causa di una failure, insieme di informazioni che quando processate producono un fallimento. In sostanza una failure è un comportamento anomalo, inatteso o errato del sistema mentre il fault è la sua causa identificata o ipotizzata. ES. Non tutti i fault generano failure, una failure può essere generata da più fault, un fault può generare diverse failure. Un defect (difetto): quando non è importante distinguere da fault e failure si può usare il termine defect per riferirsi sia alla causa fault che all effetto failure. Un Error (errore): è usato con 2 significati diversi: o una discrepanza fra un valore calcolato, osservato o misurato e il valore corretto o l azione di una persona che causa la presenza di un fault in un software. Test e casi di test Un programma è esercitato da un caso di test e un test è formato da un insieme di casi di test. Un test ha successo se rileva uno o più malfunzionamenti del programma. L oracolo è una condizione necessaria per effettuare un test: conoscere il comportamento atteso per poterlo confrontare con quello osservato. L oracolo conosce il comportamento atteso per ogni caso di prova. L oracolo umano si basa sulle specifiche o sul giudizio. L oracolo automatico è generato dalle specifiche. Viene applicato sullo stesso software ma sviluppato da altri. Il test di applicazioni grandi e complesse può richiedere milioni di casi di test. La dimensione dello spazio di uscita può eccedere le capacità umane. Per questo caso risulta necessario l utilizzo di oracoli automatici. Il settore del testing è tormentato da problemi indecidibili: un problema è detto indecidibile se è possibile dimostrare che non esistono algoritmi che lo risolvono. Un altro problema è stabilire se l esecuzione di un programma che termina a fronte di un input arbitrario è un problema indecidibile. 28

71 IS2 -> Capitolo 3 È abbastanza banale che se non posso dimostrare che un programma termina, non posso dimostrare che questo è corretto. V&V dovrebbero stabilire un certo livello di fiducia che il software è adatto ai suoi scopi (fa quello che deve fare). Questo non significa completa assenza di difetti ma significa invece che deve essere abbastanza buono per l uso per il quale è stato pensato e proprio attraverso l uso, si determinerà il livello di fiducia richiesto. Il livello di fiducia dipende dagli scopi e dalle funzioni del sistema, dalle attese degli utenti e dal time-to-market. Un programma si può ritenere analizzato a sufficienza e quindi si può terminare la fase di testing se si rispettano i seguenti criteri: 1. Criterio temporale: periodo di tempo predefinito 2. Criterio di costo: sforzo allocato predefinito 3. Criterio di copertura: percentuale predefinita degli elementi di un modello di programma ed è legato a un criterio di selezione dei test case 4. Criterio statistico: MTBF (mean time between failures) predefinito e confronto con un modello di affidabilità esistente. 5. Indecidibilità: dati due programmi il problema di stabilire se essi calcolano la stessa funzione è indecidibile. Selezione del test Un test è ideale se l insuccesso del test implica la correttezza del programma ossia se il programma fornisce dati di uscita corretti per tale test il programma è corretto per qualsiasi altro test. Il problema è proprio quello di selezionare un test ideale per un programma. Un test esaustivo è un test che contiene tutti i dati di ingresso al programma. Un test esaustivo è un test ideale ma non è pratico e quasi sempre non è affidabile. Il criterio di selezione di test specifica le condizioni che devono essere soddisfatte da un test: consente di selezionare più test per uno stesso programma. Un criterio di selezione di test è affidabile per un programma se per ogni coppia di test selezionati, T1 e T2, se T1 ha successo anche T2 ha successo e viceversa (ossia ogni insieme di casi di test che soddisfa il criterio rileva gli stessi errori). Un criterio di 29

72 IS2 -> Capitolo 3 selezione di test è valido per un programma se, qualora il programma no è corretto, esiste almeno un test selezionato che ha successo. Tutti i test ideali sono sia affidabili che validi. Goodenough e Gerhard nel 1975 hanno dimostrato che esiste un test ideale però costruire un test ideale è un problema indecidibile: non esiste un algoritmo che, dato un programma arbitrario P, generi un test ideale finito, e cioè un test definito da un criterio affidabile e valido. Un alternativa è quella della costruzione dei test case. In pratica si selezionano i casi di test che approssimano un test ideale; al di la di casi banali, non è possibile costruire un criterio di selezione generale di test valido ed affidabile che non sia il test esaustivo. Si deve cercare di massimizzare il numero di malfunzionamenti scoperti e minimizzare il numero di casi di test. È preferibile usare più di un criterio di selezione dei test: Testing dei difetti (sistematico): test progettati per scoprire in maniera sistematica i difetti del sistema: black box testing (testing funzionale) e white box testing (testing strutturale). Testing statico: test progettati per riflettere la frequenza di input degli utenti ed è usato per la stima dell affidabilità Analisi mutazionale: consente di valutare la bontà dei test. Il black box testing e white box testing sono dei test a copertura. Nessuna delle 2 è sufficiente: nella pratica, delle parti sono specificate e non implementate e viceversa. Quelle specificate e non implementate si chiamano funzionalità mancati; quelle implementate ma non specificate vengono chiamate funzionalità inaspettate. L analisi mutazionale è una tecniche che deriva dal test hardware: identificare un insieme di locazioni del programma (legate a un particolare difetto) generare programmi alternativi (mutanti) inseminando difetti nel programma originale nella locazione identificata generare casi di test stimando l adeguatezza nello scoprire difetti reali dalla capacità di rilevare i difetti inseminati. L analisi mutazionale risulta essere una tecnica ancora poco efficace per il test del software: mancano buoni modelli dei difetti. Il testing statistico è usato per il testing dell affidabilità del software. Misurando il numero di errori permette di predire l affidabilità del software. Dovrebbe prima essere specificato un livello accettabile di affidabilità e poi il software dovrebbe essere testato e corretto finché non si raggiunge il livello di affidabilità desiderato. SOFTWARE TESTING Pianificazione del testing Un attenta pianificazione è necessaria per ottenere i risultati migliori dai processi di ispezione e testing. La pianificazione dovrebbe partire presto nel processo di sviluppo e il piano dovrebbe individuare un bilanciamento tra verifica statica e testing. La pianificazione del test è basata sulla definizione di standard per il processo di testing e i suoi deliverable. 30

73 IS2 -> Capitolo 3 Il test non è una fase: l attività di analisi e test sono e devono essere presenti per tutta la durata del ciclo di vita (controllo di qualità). Il processo deve essere pianificato per garantire visibilità il prima possibile e continuamente. I test sono parte del prodotto finale, devono essere sviluppati con codice e documentazione e devono essere salvati per la manutenzione. La struttura del processo e del prodotto può essere adattata per facilitare e migliorare la misura di qualità. Ci si chiede chi deve fare testing: lo sviluppatore o un team esterno??? ENTRAMBI!!!! Lo sviluppatore è colui che ha sviluppato il sistema quindi i suoi test sono inquinati da aspetti psicocognitivi: perché deve distruggere il mio sistema? Un team esterno, invece, prima deve comprendere il sistema però fa tutti gli sforzi per poterlo distruggere. Le caratteristiche di un buon modello di test è che deve contenere dei casi di test in grado di identificare i difetti. Il test deve includere una grande quantità di input, includendo valori di input non validi e testando i casi limite, altrimenti non sarebbe possibile individuare i malfunzionamenti. Esistono diversi livelli di testing alcuni dei quali sono a carico del produttore, altri del produttore/cliente ed altri solo dei clienti. Le principali attività di testing si suddividono in: Test planning Usability testing Unit testing Integration testing System testing o Functional testing o Performance testing o Acceptance testing e installation testing Il produttore effettua lo unit testing, integration testing, system testing. La cooperazione tra produttore e cliente effettuano l alfa e il beta testing mentre il cliente effettua l acceptance testing. Il testing di unità è applicato isolatamente ad un unità in un sistema software. L obiettivo fondamentale è quello di rilevare 31

74 IS2 -> Capitolo 3 errori nel modulo ed è una prassi diffusa che viene realizzata direttamente dal programmatore che ha sviluppato l unità che è sottoposta a testing. Per unità si intende un elemento definito nel progetto di un sistema software e testabile separatamente. Bisogna creare l ambiente per l esecuzione dei test. Il testing di unità si focalizza su componenti del sistema software, ovvero su oggetti e sottosistemi. Ci sono tre motivazioni dietro per cui ci si focalizza sulle componenti. 1. Primo: il testing di unità riduce la complessità, in generale, sulle attività di test. 2. Secondo: eseguire il test di unità è più facile che identificare e correggere gli errori 3. Terzo: il test di unità permette il parallelismo nelle attività di test. Lo scaffolding è un insieme di software per testare il prodotto. Può richiedere uno sforzo elevato di programmazione ed uno scaffolding buono è un passo importante per test di regressione. Eseguire dei test su una singola componente o su una combinazione di componenti richiede che le componenti siano isolate dal resto del sistema. Il test driver e il test stub sono utilizzati come sostituti per le parti mancanti del sistema (in precedenza isolato dalle componenti da testare). Il test driver deve simulare l ambiente chiamante ed occuparsi dell inizializzazione dell ambiente non locale del modulo in esame. Il test driver passa gli input test identificati nel test case di analisi del componente e visualizza i risultati. Il test stub (modulo fittizio) ha la stessa interfaccia (API) del modulo simulato ma è più semplice, esso ritorna il risultato sottomesso con il ritorno del tipo di signature richiesta. Se l interfaccia di un componente cambia, il corrispondente test driver e test stub deve cambiare. Il driver e lo stub possono essere: Interattivi: viene richiesto l intervento umano e può risultare troppo oneroso e poi l utente potrebbe introdurre errori. Automatici: il driver inizializza le costanti dell ambiente non locali e lo stub calcola il valore approssimato, e restituisce i valori delle costanti. Realizzare dei prototipi: realizzazione rapida e con costi ridotti, a scapito dell efficienza. 32

75 IS2 -> Capitolo 3 Costruiti a partire da specifiche: stesse metodologie di sviluppo del software da rilasciare. Non è molto costoso e restituisce determinati valori dopo aver inserito gli input. Il costo risulta essere quello relativo allo studio della specifica. Software OO e Unit Testing I livelli tradizionali mal si adattano al caso di linguaggi Object Oriented. È possibile suddividere: Basic unit testing: testing di una singola operazione di una classe Unit testing: test di una classe nella sua globalità. I linguaggi procedurali standard sono costituiti da una componente di base che di solito è una procedura e da un modulo di test. Nel linguaggio OO la componente di base è una classe, gli oggetti sono istanze di classi, la correttezza non è legata solo all output ma anche allo stato definito dallo struttura dati. Lo stato privato può essere osservato solo utilizzando metodi pubblici della classe. Lo scaffolding per software OO deve rispettare le seguenti caratteristiche: l infrastruttura deve settare opportunamente lo stato per poter eseguire i test, deve esaminare lo stato per poter stabilire la correttezza dei test, il tutto utilizzando approcci intrusivi quali modificare il codice sorgete aggiungendo un metodo test driver alla classe e usare costrutti del linguaggio (esempio costrutto friend). Il test di integrazione richiede la costruzione di un sistema partendo dai suoi componenti, e il test del sistema risultante per problemi che derivano dall integrazione di componenti. I componenti integrati possono essere prefabbricati, riutilizzabili o prodotti da zero. Il test di integrazione controlla che questi componenti lavorino effettivamente insieme, siano chiamati in modo corretto, e trasferiscano i dati giusti al momento giusto attraverso le loro interfacce. Il testing di integrazione ha come obiettivo fondamentale quello di rilevare degli errori nella integrazione fra le unità e nelle funzioni che l aggregato deve assolvere. È applicato ad un aggregato di 2 o più unità di un sistema software. Non è compito dei programmatori che hanno prodotto le unità componenti e talvolta il termine testing di interazione viene riferito anche alla integrazione fra componenti hardware e software. Gli errori di integrazione possono essere suddivisi in: Interpretation error: funzionalità/comportamento del modulo diversi da quelli attesi dall utente del modulo Miscoded Call Error: istruzione di chiamata (messaggio) inserita nel posto sbagliato (o non inserito dove richiesto) Interface Error: violazione dell interfaccia standard tra due moduli (cattiva specifica/implementazione di una interfaccia). Il testing di integrazione viene effettuato il 2 modalità: 1. Big bang test (test non incrementale): testare prima tutti i moduli quindi integrare i moduli testati e verificare l intero sistema. Questa strategia assume che tutte le componenti vengano testate per prime individualmente e testate tutte su un singolo sistema. Il vantaggio è che non sono necessari test stub e driver addizionali. Anche se questa strategia suona semplice, il big bang testing è espansivo: se il test scopre dei fault, è impossibile distinguere la failure nell interfaccia dalla failure generata dalla componente che richiama quell interfaccia. A maggior ragione, la difficoltà di identificare lo specifico componente responsabile della failure, tutti i componenti del sistema sono potenziali colpevoli. 2. Testing incrementale: integrazione dei moduli man mano che vengono prodotti e testati. Questo porta a molti vantaggi quali: meno moduli fittizi e moduli guida; individuazione ed eliminazione di anomalie sulle interfacce durante lo sviluppo del sistema; anomalie più facilmente localizzabili e rimovibili; ciascun modulo è esercitato più a lungo. Da queste modalità ci sono altre derivate quali top-down, bottom-up e sandwich. 33

76 IS2 -> Capitolo 3 Il testing incrementale top-down ha il vantaggio di sviluppare, testare ed integrare prima moduli più in alto nella gerarchia delle chiamate (moduli di coordinamento). Non richiede moduli driver ma richiede moduli stub piuttosto complessi. Pospone il testing di moduli di ingresso/uscita e di moduli di elaborazione. Il programma di test top-down è rappresentato da una singola componente astratta con dei sub-component rappresentati dagli stub. Lo stub ha la stessa interfaccia dei componenti che l hanno chiamato ma con molte funzionalità ridotte. Successivamente i componenti di più alto livello vengono programmati e testati, i sub-component sono programmati e testati allo stesso modo. Questo processo continua fino a quando i componenti a più basso livello non sono testati. Nel testing incrementale bottom-up lo sviluppo e il test avvengono prima dei moduli periferici della gerarchia delle chiamate. Ha la necessità di moduli driver e non richiede stub. Il testing bottom-up coinvolge l integrazione e il testing dei componenti a più basso livello nella gerarchia, e si testano tutte le componenti salendo fino al modulo più in alto presente nella gerarchia. Questo approccio non richiede un design dell architettura del sistema (architectural design) per essere completato e può facilmente partire dal primo stage in fase di sviluppo. Il migliore è quello sandwich che è un sistema misto tra quello top-down e bottom-up. Durante il sandwich test, il tester è capace di riformulare la mappa del sottosistema in tre livelli: Target layer Layer sopra il target layer Layer sotto il target layer Usando come strato obiettivo il target layer, il test top-down e il test bottom-up possono essere eseguiti in parallelo. Il test di integrazione top-down è attivo per testare in modo incrementale i livelli in alto con i componenti del target layer, e il bottom-up test è usato per testare i livelli in basso con i componenti del target layer. Come risultato, i test stub e i test driver non sono necessari per i livelli top e bottom, perché essi usano gli attuali componenti del target layer. Nel caso di software OO è preferibile una strategia bottom-up dove si testano prima le classi indipendenti. Il generale il big bang è poco adatto. Posso realizzare un grafo delle dipendenze tra le classi: se il grafo è aciclico esiste un ordinamento parziale sui suoi elementi; se esistono dipendenze cicliche tra le classi è impossibile definire un ordinamento parziale ma ogni grafo ciclico è riducibile ad un grafo aciclico collassando i sottografi massimi fortemente connessi. Una volta definito l ordine di integrazione si aggiungono le classi incrementalmente esercitandone le integrazioni. I possibili problemi di integrazioni per software OO sono: il primo legato all ereditarietà che implica problemi in caso di modifiche di superclassi e il secondo è il polimorfismo che comporta problemi legati al binding dinamico. 34

77 IS2 -> Capitolo 3 Il testing di sistema Il testing di unità e di integrazione sono focalizzati ad individuare fault in componenti individuali e interfacce tra le componenti. Quando ogni componente è stata integrata, il testing di sistema assicura che l intero sistema si attenga ai requisiti funzionali e non funzionali. Functional testing: test dei requisiti funzionali (dal RAD). Trova le differenze tra i requisiti funzionali e il sistema. Il test di sistema è una black-box: i casi di test sono derivati dall uso dei modelli dei casi d uso. Performance testing: trova le differenze tra i design goal selezionati durante il system design e il sistema. Perché i design goals non sono derivati dai requisiti non funzionali, i test cases non possono essere derivati dal SDD o dal RAD. Nella fase di performance testing sono usati diversi tipi di test: o stress testing: controlla se il sistema può rispondere a molte richieste contemporaneamente. o volume testing: tenta di individuare faults con una grande quantità di dati o security testing: tenta di individuare dei faults all interno del sistema o timing testing: tenta di individuare il comportamento del sistema nella riparazione di uno stato di errore o recovery tests: capacità di reazione del sistema a cadute Pilot testing: durante la fase di test pilota, il sistema è installato e usato da un insieme di utenti selezionati. Non ci sono specifiche linee guida o scenari di test che gli utenti devono eseguire. Il test pilota è utile quando il sistema è costruito senza un insieme di specifiche o richieste o senza specifiche da occuparsi per il cliente. In questo caso, un gruppo di persone è invitato a usare il sistema per un tempo limitato e di fornire dei feedback agli sviluppatori. o L alpha testing è costituito dall uso del sistema da parte di utenti reali ma nell ambiente di produzione e prima della immissione sul mercato e talvolta è riferito da parte di un cliente e gruppo di clienti privilegiati. o Il beta testing riguarda l installazione ed uso del sistema in ambiente reale prima della immissione sul mercato. È una strategia adottata da produttori di software per mercato di massa e talvolta il beta testing è proceduto da un alpha testing o un meta testing da parte di un gruppo più ristretto di utenti privilegiati. L unico problema che può sorgere dal beta testing sono legati alla confidenzialità. Acceptance testing: testing effettuato sull'intero sistema sulla base di un piano e di procedure approvate dal cliente. L'obiettivo è quello di mettere il cliente, l'utente o altri a ciò preposti (collaudatori o enti ad hoc) in condizioni di decidere se accettare il prodotto. E' a carico del committente e segna il passaggio del sistema dal produttore all'ambiente operativo. Può talvolta essere più una demo che un test. I test di benchmark possono essere eseguiti dagli utenti reali o da team speciali di testers che provano le funzionalità del sistema, ma è importante che i tester abbiano familiarità con i requisiti funzionali e non funzionali in modo tale che siano in grado di valutare il sistema. Dopo tale test, il cliente riporta al project manager i requisiti che non sono stati soddisfatti. Questo consente di instaurare un dialogo tra i clienti e gli sviluppatori per discutere sui cambiamenti, modifiche, aggiunte, etc. al sistema. Installation testing: dopo che il sistema viene accettato, viene installato nell'ambiente per cui è stato sviluppato. Il test di installazione ripete i test case eseguiti durante il test di funzionalità e di performance. Se il test è positivo il prodotto può essere consegnato e messo in funzione. 35

78 IS2 -> Capitolo 3 I deliverable di un processo di testing sono dei documenti in formato cartaceo od elettronico e costituiscono un modo per fissare riferimenti comuni per i processi di testing. I documenti di pianificazione e specifica sono: TP (test plan) (piano dei test): documento che descrive l oggetto, l approccio generale, le risorse e lo scheduling delle attività da realizzare. Identifica i test item, le caratteristiche da testare, le attività di testing. Identifica i rischi e i piani di emergenza ed i criteri pass/fail. TDS (test design specification) (specifiche dei test): documento che specifica per una o più caratteristiche da testare i dettagli dell approccio al testing (tecniche di testing, analisi dei risultati, lista dei test case con la loro motivazione ad attributi generali). TCS (test case specification) (casi di prova): è un documento che contiene gli input, i driver, gli stub ed i risultati attesi (oracolo) dei test come pure i task da eseguire. TPS (test procedure specification) (procedure di esecuzione): è un documento che specifica per una o più test case i passi da fare per eseguirli ed in particolare come preparare l esecuzione della procedura, come avviare e condurre tale esecuzione, quali rilevazioni e misure fare, come sospendere il test in presenza di eventi imprevisti e come avviarli nuovamente. Termini: o Test item: codice sorgente, codice oggetto, job control, data control, etc. del software da sottoporre a testing. Un test item è accompagnato dalla relativa documentazione (requisiti specifiche progetto). o Pass/fail criteria: regole di decisione da usare per stabilire se una caratteristica software supera o meno il test. 36

79 IS2 -> Capitolo 3 I documenti di esecuzione sono: TTR (test item transmittal report) (documento di accompagnamento): è un documento che deve accompagnare ogni software item consegnato al testing. È almeno costituito dalle informazioni di identificazione del software item, del suo stato e della sua allocazione fisica. TL (test log) (archivio di esecuzione): è la banca dati della memorizzazione sistematica, strutturata ed in ordine cronologico di tutti i dettagli rilevanti sulla esecuzione dei test; informazioni fondamentali di tale documento sono il successo o l'insuccesso dei test, l'occorrenza e la descrizione di eventi anomali e di test-incident; Test Incident: ogni evento occorso in un processo di testing e che richiede altre e più approfondite analisi ed investigazioni. TIR (test incident report) (documento degli eventi rilevanti): è un documento che descrive i testincident che si sono verificati. Vengono inserite le informazioni riguardanti gli input, i risultati attesi, i risultati attuali, le anomalie, data e ora, i tentativi di rieseguire il test, gli addetti al testing. TSR (test summary report) (report finale): è un sommario ed una valutazione di una o più attività di testing. I componenti fondamentali di tale documento sono la lista degli incidenti risolti e delle relative soluzioni, la lista degli incidenti irrisolti, una valutazione dei limiti del test. La comprehensiveness è una valutazione di quanto il test sia esaustivo rispetto agli obiettivi previsti nel piano (vi sono caratteristiche non sufficientemente testate e le ragioni di ciò). I documenti di testing assumeranno un ruolo di straordinaria importanza nell era della qualità: i documenti di testing diventeranno una essenziale sorgente di informazioni per la valutazione e la certificazione di qualità. La produzione e la manutenzione di tali documentazione è stata ritenuta tediosa e costosa nel passato (un alibi per giustificare la totale assenza di ben definiti processi di testing). Le tecnologie hardware e software odierne fanno cadere tali alibi grazie all esistenza di supporto automatici per la memorizzazione e gestione automatica di tale documentazione, la rende obbligatoria in ogni ambiente di produzione, manutenzione ed evoluzione. La organizzazione e la implementazione di una REPOSITORY DI TESTING è ormai un supporto indispensabile. I documenti e la loro struttura vanno adattati all ambiente e alla maturità dei processi di testing che in esso si sviluppa. ANALISI STATICA Analisi statica in compilazione I compilatori effettuano una analisi statica del codice per verificare che un programma soddisfi particolari caratteristiche di correttezza statica. Per poter generare il codice oggetto effettuano diverse analisi: analisi lessicale: consiste nell identificazione dei singoli elementi (token), componenti il programma (keywords, identificatori, simboli del linguaggio) analisi sintattica: consiste nell esaminare le relazioni tra gli elementi identificati durante l analisi lessicale, che devono obbedire alle regole della grammatica del linguaggio analisi semantica: rileva altri errori, come l utilizzo di variabili non dichiarate, effettua il controllo dei tipi nelle espressioni. Le informazioni e le anomalie che può rilevare un compilatore dipendono dalle caratteristiche del linguaggio e dalle facility di cui esso dispone. Es.: linguaggi con regole di visibilità statica dei nomi permettono la rilevazione di un maggiore numero di anomalie di quelli con regole di visibilità dinamiche. La generazione di Cross Reference List risulta molto utile in successive analisi del codice per l individuazione di anomalie non rilevabili dal compilatore. Le tipiche anomalie identificabili sono i nomi di identificatori non dichiarati, incoerenza tra tipi di dati coinvolti in una istruzione, incoerenza tra parametri formali ed effettivi in chiamate a subroutine, codice non raggiungibile dal flusso di controllo. 37

80 IS2 -> Capitolo 3 Per realizzare quest analisi, abbiamo bisogno di rappresentazioni del programma tramite grafi. Raggiungibilità e grafi non orientati Un nodo m si dice raggiungibile da un nodo n se esiste un cammino di lunghezza k-1 <n1, n2,..., nk>, tale che n1 = n e nk = m Esempi: f è raggiungibile da a, g è raggiungibile da a, f non è raggiungibile da d. Un grafo orientato si dice connesso se ogni suo nodo è raggiungibile da ogni altro nodo sul grafo che si ottiene eliminando l orientamento degli archi. Un grafo orientato si dice fortemente connesso se ogni nodo è raggiungibile da ogni altro nodo (ossia se esiste un ciclo che coinvolge tutti i nodi del grafo). Il Grafo del Flusso di Controllo di un programma P è una quadrupla GFC(P) = (N, E, ni,nf) dove: (N,E) è un grafo diretto con archi etichettati e ni ed nf sono detti nodo iniziale e nodo finale ni N, nf N,N - {ni, nf} = Ns Np Ns e Np sono insiemi disgiunti di nodi, ossia che rappresentano rispettivamente istruzioni e predicati E (N - nf) (N - ni) {true, false, uncond} rappresenta la relazione di flusso di controllo. Un nodo in Ns {ni}ha un solo successore immediato ed il suo arco è etichettato con uncond. Un nodo in Np ha due successori immediati e i suoi archi uscenti sono etichettati rispettivamente con true e false. Un GFC(P) e' ben formato se esiste un cammino dal nodo iniziale ni ad ogni nodo in N-{ni} e da ogni nodo in N-{nf} al nodo finale nf. Diremo semplicemente cammino o cammino totale un cammino da ni a nf. Sequenza di nodi possono essere collassate in un solo nodo, purché nel grafo semplificato vengano mantenuti tutti i branch (punti di decisione e biforcazione del flusso di controllo) e tale nodo può essere etichettato con i numeri dei nodi in esso ridotti. Analisi del flusso di controllo Il flusso di controllo è esaminato per verificarne la correttezza. Il codice è rappresentato tramite il Control flow Graph (CfG), i cui nodi rappresentano statement (istruzioni e/o predicati) del programma e gli archi il passaggio del flusso di controllo. 38

81 IS2 -> Capitolo 3 Il grafo è esaminato per identificare ramificazioni del flusso di controllo e verificare l esistenza di eventuali anomalie quali codice irraggiungibile e non strutturazione. Consente di rilevare anomalie sull utilizzo di variabili sui diversi cammini di esecuzione. Le operazioni eseguite dalle istruzioni su una variabile x: definizione (d): assegna un valore alla variabile x (istruzioni di assegnamento e istruzioni di input) uso (u): usa il valore della variabile x in un istruzione di output, in una espressione per il calcolo di un altra variabile, o in un predicato valutando il valore effettivo della variabile annullamento (a): al termine dell esecuzione dell istruzione il valore assegnato alla variabile non è più significativo Es.: nell espressione a:=b+c; la variabile a è definita mentre b e c sono usate. La definizione di una variabile, così come un annullamento, cancella l effetto di una precedente definizione della stessa variabile, ovvero ad essa è associato il nuovo valore derivante dalla nuova definizione (o il valore nullo). Regole R1: L uso di una variabile x deve essere sempre preceduto in ogni sequenza da una definizione della stessa variabile x, senza annullamenti intermedi. Un uso non preceduto da una definizione può corrispondere al potenziale uso di un valore non determinato. R2: Una definizione di una variabile x deve essere seguita da un uso della variabile x, prima di un altra definizione o di un annullamento di x. Una definizione non seguita da un uso corrisponde all assegnamento di un valore non utilizzato e quindi potenzialmente inutile. Attraverso le tecniche di analisi statistica, posso individuare questi errori. Espressioni cammino/variabile L espressione relativa ad un cammino p di un programma P per la variabile x è indicata con P(p;x) 39

82 IS2 -> Capitolo x =.. 3 if. x =. 4. =. x. P( [, 2, 4,.]; x) = ( du ) P( [, 2, 3, 4,.]; x) = ( ddu ) Le Espressioni regolari vengono usate per rappresentare espressioni relative ad una variabile corrispondente a più cammini. Un espressione regolare è definita a partire da un alfabeto finito A, nel nostro caso A = {a, d, u, -} e dalle seguenti regole ricorsive: ε, stringa nulla, è un espressione regolare ogni simbolo di A è un espressione regolare se e1 ed e2 sono espressioni regolari, allora lo sono anche le espressioni che si formano da queste con l uso degli operatori: sequenza (.), alternativa (+) e ciclo (*); quindi e1.e2, e1+e2, e1* sono espressioni regolari niente altro è un espressione regolare Le espressioni regolari vengono usate per modellare cammini multipli (costoso costruire un espressione regolare per ogni variabile) e per algoritmi per l analisi del flusso dati efficienti: reaching definitions: quali istruzioni (nodi del GFC) una definizione di una variabile x raggiunge senza essere uccisa da una nuova definizione di x? reacheable uses: da quali definizioni della variabile x è raggiungibile un uso della stessa variabile? situazioni anomale: se una definizione di x non raggiunge nessun uso di x, o se un uso di x non è raggiunto da nessuna definizione di x. Esecuzione Simbolica Il programma non è eseguito con i valori effettivi ma con valori simbolici dei dati di input. L esecuzione procede come una esecuzione normale ma non sono elaborati valori bensì formule formate dai valori simbolici degli input. Gli output sono formule dei valori simbolici degli input. L esecuzione simbolica anche di programmi di modeste dimensioni può risultare molto difficile: dovuto all esecuzione delle istruzioni condizionali: deve essere valutato ciascun caso (vero e falso); necessità di theorem proving; in programmi con cicli ciò può portare a situazioni difficilmente gestibili. Path Conditions Diremo path condition (pc) la relazione composta (deducibile dai predicati che giacciono sul cammino) che 40

83 IS2 -> Capitolo 3 deve essere soddisfatta dai dati d'ingresso del programma perché la sequenza di istruzioni associata ai nodi del cammino sia eseguita. Una pc è un espressione Booleana sugli input simbolici di un programma. All inizio dell esecuzione simbolica essa assume il valore vero (pc := true ). Per ogni condizione che si incontrerà lungo l esecuzione pc assumerà differenti valori a seconda dei differenti casi relativi ai diversi cammini dell esecuzione Ogni foglia dello execution tree rappresenta un cammino che sarà percorso per certi insiemi di valori di input. Le pc associate a due differenti foglie sono distinte; ciascuna foglia dello execution tree rappresenta un cammino che sarà percorso per la pc ad essa associata. Non esistono esecuzioni per cui sono vere contemporaneamente più pc (per linguaggi di programmazione sequenziali). Se l output ad ogni foglia è corretto allora il programma è corretto. Ma, quanti rami può avere un execution tree? Diremo cammino eseguibile (feasible path) un cammino per il quale esiste un insieme di dati in ingresso che soddisfa la path condition. Diremo cammino non eseguibile (infeasible path) un cammino per il quale non esiste un insieme di dati di ingresso che soddisfa la path condition. L uso di un theorem prover durante l esecuzione simbolica può semplificare le path conditions ed eliminare infeasible paths, ma Il problema di dimostrare che due cammini di un programma calcolano la stessa funzione è indecidibile. Il problema di dimostrare l'equivalenza di due espressioni simboliche di due cammini di un programma è indecidibile. Davis 1973: il problema di stabilire se esiste una soluzione per un sistema di diseguaglianze è indecidibile. Un cammino è eseguibile se esiste un punto nel dominio di ingresso che rende soddisfatta la sua path condition (un sistema di diseguaglianza). La determinazione della feasibility o infeasibility di un cammino è indecidibile. Tuttavia se si riesce a dimostrare che ciascun predicato nella path condition è dipendente linearmente dalle variabili di ingresso allora il problema è risolubile con algoritmi di programmazione lineare. Bisogna stabilire se: una istruzione sarà eseguita per un punto del dominio di ingresso un ramo (branch: corrisponde ad un arco del GFC) sarà attraversato per un punto del dominio di ingresso un cammino sarà eseguito per un punto del dominio di ingresso per ogni istruzione di un programma esiste un insieme definito di punti del dominio di ingresso che ne implica l'esecuzione per ogni ramo di un programma esiste un insieme definito di punti del dominio di ingresso che ne implica l'esecuzione per ogni cammino di un programma esiste un insieme definito di punti del dominio di ingresso che ne implica l esecuzione 41

84 IS2 -> Capitolo 3 TESTING FUNZIONALE: BLACK BOX La definizione del casi di test e dell oracolo è fondata sulla base della sola conoscenza dei requisiti specificati del sistema e dei suoi componenti. Il testing funzionale è scalabile, usabile per i diversi livelli di testing ma non riesce a rilevare difetti che dipendono dal codice. Si specificano gli input e gli output e sulla base di questo riesco a definire dei casi di test. Molte tecniche sono abbastanza generalizzate e possono essere usate sia per software tradizionale che OO. Il sistema è una black box il cui comportamento può essere determinante per studiare i suoi input e i suoi relativi output. Il black box testing è chiamato anche testing funzionale, perché il collaudatore è interessato solo alle funzionalità e non all implementazione del software. La figura illustra il modello del sistema che è assunto in un black box testing. Questo approccio è ugualmente applicabile a sistemi che sono organizzati in funzioni o in oggetti. Il collaudatore presenta gli input ai componenti o al sistema ed esamina i corrispondenti output. Se gli output non sono quelli predetti vuol dire che il test ha identificato con successo dei problemi con il software. La chiave del problema per testare la presenza di difetti è quella di selezionare degli input che hanno alta probabilità di appartenere all insieme degli input che causano anomalie (in figura ). In molti casi, la selezione di questi casi di test è basata su precedenti esperienze nel test engineers (pianificazione test). Essi usano un dominio di conoscenza per individuare ed identificare i casi d uso che sono buoni per rilevare i difetti. Le tecniche di test funzionale possono essere: 1. Funzionalità esterne: funzionalità visibili all utente e definite dai requisisti e dalle specifiche 2. Funzionalità interne: funzionalità non visibili all utente e definite dal progetto di alto e basso livello. I metodi sono quelli basati sia su specifiche formali (specifiche algebriche, macchine a stati finiti, statecharts), sia metodi basati su specifiche semi-formali e sia metodi basati su specifiche informali. I criteri di copertura per testing funzionale è eseguire almeno una volta ogni funzionalità e per ognuna di queste bisogna effettuare un numero di esecuzioni dedotte dai dati di ingresso e di uscita, da precondizioni e postcondizioni: definito il dominio dei dati di I/O effettuare dei test case ottenuti selezionando: Valori in posizione centrale Valori ai bordi Valori speciali precondizioni e postcondizioni per test case: Positive Negative (dati di input ed output invalidi) Neutrali Bisogna suddividere i test case in classi di equivalenza. Suddivisione in classi di equivalenza Il dominio dei dati di ingresso è partizionato in classi di casi di test in modo tale che, se il programma è corretto per un caso di test, si possa dedurre ragionevolmente che è corretto per ogni caso di test in quella 42

85 IS2 -> Capitolo 3 classe. Una classe di equivalenza rappresenta un insieme di stati validi o non validi per una condizione sulle variabili di ingresso. I dati di input al programma usualmente cadono dentro un numero differenti di classi. Queste classi hanno come caratteristiche di essere: numeri positivi, numeri negativi, stringhe senza spazi, etc. Normalmente i programmi agiscono in modo comparabile con tutti i membri delle classe. Siccome hanno questo comportamento equivalente, queste classi sono talvolta chiamate domini di equivalenza o partizioni di equivalenza. Un approccio sistematico per disertare il testing è basato sull identificare tutte le classi di equivalenza a cui un programma deve attenersi. Gli input a una classe di equivalenza sono insiemi di dati dove tutto l insieme dei membri devono processarli allo stesso modo. L output delle classi di equivalenza sono programmi, che hanno in comune Una volta identificato un insieme di classi di equivalenza, si deve scegliere un caso di test da questo insieme. Una buona linea guida per seguire la selezione del caso di test è di scegliere il test case sul confine dell insieme Es. Ogni input può essere partizionato in classi di equivalenza. Nell esempio sopra, le 3 variabili vengono partizionati in classi di equivalenza secondo 2 criteri: weak (deboli) e strong (forti). Nel debole si prendono dei valori dalla classe di equivalenza e si effettuano i casi di test (nel nostro esempio il minimo è 4 (B) casi di test) mentre in quello forte, si verificano tutte le possibili combinazioni tra le classi (nel nostro caso sono necessari 24 casi di test 3x4x2). Esempio: NextDate NextDate è una funzione con tre variabili: month, day, year. Restituisce la data del giorno successivo alla data di input tra gli anni 1840 e Specifica sommaria: se non è l ultimo giorno del mese, la funzione incrementa semplicemente il giorno. Alla fine del mese il prossimo giorno è 1 e il mese è incrementato. Alla fine dell anno, giorno e mese diventano 1 e l anno è incrementato. Considerare il fatto che il numero di giorni del mese varia con il mese e l anno bisestile. 43

86 IS2 -> Capitolo 3 La tecnica delle classi di equivalenza è una tecnica base e si usano poco perché di solito sono pensate per classi di equivalenza che non fanno parte dello stesso dominio (classi validi). Se le condizioni di errore hanno alta priorità, bisogna estendere il criterio forte (SECT) includendo anche classi non valide tramite Test di robustezza. Il metodo delle classi di equivalenza è appropriato quando i dati di input sono definiti in termini di intervalli e insiemi di valori discreti. Il criterio forte (SECT) assume che le variabili sono indipendenti le dipendenze generano error test cases. E possibile che ce ne siano molti ed esistono metodi che analizzano le dipendenze tra le variabili e riducono i casi di test. Classi non valide e selezione delle classi di equivalenza La condizione sulle variabili d ingresso specifica: o intervallo di valori dove almeno una classe valida per valori interni all intervallo, una non valida per valori inferiori al minimo, e una non valida per valori superiori al massimo; o elemento di un insieme discreto (enumerazione) dove una classe valida per ogni elemento dell insieme, una non valida per un elemento non appartenente, include il caso di valori specifici ed include il caso di valori booleani. Ogni classe di equivalenza deve essere coperta da almeno un caso di test. Ciascun caso di test per le classi valide deve comprendere il maggior numero di classi valide ancora scoperte e bisogna realizzare un caso di test per ogni classe non valida. Progettazione dei casi di test Le classi di equivalenza valide devono essere utilizzate per identificare casi di test che minimizzino il numero complessivo di test e risultino significativi (affidabili); si devono individuare tanti casi di test da 44

87 IS2 -> Capitolo 3 coprire tutte le classi di equivalenza valide, con il vincolo che ciascun caso di test comprenda il maggior numero possibile di classi valide ancora scoperte. Le classi di equivalenza non valide devono individuare tanti casi di test da coprire tutte le classi di equivalenza non valide, con il vincolo che ciascun caso di test copra una ed una sola delle classi non valide. Testing dei valori limite (boundary values) Il metodo delle classi di equivalenza partiziona il dominio di ingresso assumendo che il comportamento del programma su input della stessa classe è simile. I tipici errori di programmazione capitano al limite tra classi diverse e il testing dei valori limiti si focalizza su questo aspetto. E più semplice e serve a complementare la tecnica precedente. È lontano rispetto i valori che vado ad esprimere nel codice. Gli svantaggi delle classi di equivalenza e test al limite (boundary) è che queste tecniche non esplorano le combinazioni dei dati di test passati in input. In molti casi, il programma fallisce perché la combinazione di certi valori causano stati di errore. Le cause e gli effetti del testing è dovuto a problemi di stabilizzare relazioni logiche tra input e output o da input e trasformazioni. Gli input sono la causa, e gli output o trasformazioni sono gli effetti. La tecnica è basata su una premessa che gli input e output possano essere trasformati in una funzione Booleana. Consideriamo una funzione F, con due variabili x1 e x2 dove i limiti (possibilmente non definiti) sono: a <= x1 <= b c <= x2 <= d In alcuni linguaggi di programmazione con strong typing, è possibile specificare tali intervalli. Il metodo si focalizza sui limiti dello spazio di input per individuare i casi di test. Studi empirici hanno dimostrato che gli errori tendono a capitare vicino ai valori limite delle variabili di input. L idea di base è quella di verificare i valori della variabile di input al minimo, immediatamente sopra il minimo, un valore intermedio (nominale), immediatamente sotto il massimo e al massimo. Per convenzione indichiamo : min, min+, nom, max-, max. Bisogna mantenere tutti i valori delle variabili, eccetto una, al loro valore nominale, mentre l altra assume i valori estremi facendo variare una sola di queste variabili. Una funzione con n variabili richiede 4n + 1 casi di test. Funziona bene con variabili che rappresentano quantità fisiche limitate. Non considera la natura della funzione e il significato delle variabili ed è una tecnica rudimentale che tende al test di robustezza. Un altra tecnica Worst Case Testing (WCT) assume che la maggior parte delle failure sono originate da un solo fault. Cosa succede quando più di una variabile ha un valore estremo? L idea viene dall analisi dei circuiti in elettronica. Si costruisce un prodotto cartesiano di {min, min+, nom, max-, max}. Risulta chiaramente più profonda dell analisi dei valori limite, ma molto più costosa: 5 n test 45

88 IS2 -> Capitolo 3 cases. È una buona strategia quando le variabili fisiche hanno numerose interazioni e quando le failure sono costose. Metodi per ridurre i casi di prova L obiettivo è quello di ridurre il numero (esponenziale) di combinazioni considerando solo i casi più significativi quali: partizione delle categorie basato sull identificazione di categorie di input e relazioni di incompatibilità; tabelle delle decisioni basato sulla costruzione di tabelle per identificare i casi più significativi; grafo causa effetto basato sulla costruzione di grafi per ridurre il numero di combinazioni in base agli effetti delle diverse combinazioni. Il sistema è diviso in funzioni che possono essere testate indipendentemente. Il metodo individua i parametri di ogni funzione e per ogni parametro individua categorie distinte. Oltre ai parametri, possono essere considerati anche gli oggetti dell ambiente e le categorie sono le principali proprietà o caratteristiche. Le categorie sono ulteriormente suddivise in scelte allo stesso modo in cui si applica la partizione in classi di equivalenze (possibili valori). Vengono individuati i vincoli che esistono tra le scelte, ossia in che modo l occorrenza di una scelta può influenzare l esistenza di un altra scelta. Vengono generati Test frames che consistono di combinazioni valide di scelte nelle categorie e i Test frames sono quindi convertiti in test data. Esempio (parametri, categorie e scelte). Una funzione che prende in ingresso un array di lunghezza variabile di qualunque tipo e restituisce l array ordinato (in accordo a qualche criterio) e i valori massimo e minimo. Categorie per il parametro array: dimensione dell array, tipo degli elementi, massimo valore, minimo valore, posizioni dei valori massimo e minimo nell array. Scelte per la dimensione dell array: array di dimensione 0, di dimensione 1 e di dimensione da 2 a n (dove n è la dimensione massima) e un tentativo con n+1 (classe non valida) Scelte per posizione: il valore massimo in prima posizione, posizione centrale e ultima posizione dell array. Esempio Completo Specifica: Il programma chiede all utente un intero positivo nell intervallo 1-20 e quindi una stringa di caratteri di quella lunghezza. Il programma chiede all utente un carattere e restituisce la prima posizione nella stringa in cui il carattere viene trovato o un messaggio che indica che il carattere non è presente nella stringa. L utente ha l opzione di cercare più caratteri. 46

89 IS2 -> Capitolo 3 Parametri e Categorie: Scelte: Tre parametri: l intero x (lunghezza), la stringa a e il carattere c Categorie per x: in-range (1-20) o out-of-range Categorie per a: lunghezza minima, massima, intermedia Categorie per c: il carattere appare all inizio, al centro o alla fine della stringa, oppure non appare nella stringa Intero x, out-of-range: 0, 21 Intero x, in-range: 1, 2-19, 20 Stringa a: 1, 2-19, 20 Carattere c: prima posizione, ultima posizione, posizione centrale, non in stringa Note: a volte è possibile che ci sia solo una scelta per una categoria L individuazione di parametri, condizioni di ambiente e categorie dipende fortemente dall esperienza del tester. Rende le decisioni di testing esplicite (e.g., vincoli) e aperte a revisioni. Una volta completato il primo passo, la tecnica è semplice e può essere automatizzata. Riducendo i casi di test, la tecnica è utile e rende il testing sistematico e più praticabile. Le Tabelle di decisione aiutano ad esprimere test requirements in una forma direttamente comprensibile e usabile. Supporta la generazione automatica o manuale di casi di test; una particolare risposta o un sottoinsieme di risposte deve essere selezionato valutando molte condizioni corrispondenti. È ideale per descrivere situazioni in cui un numero di combinazioni di azioni sono prese in corrispondenza di insiemi di condizioni variabili, come ad esempio nei sistemi di controllo. La sezione delle condizioni contiene condizioni e combinazioni di queste. Una condizione esprime relazioni tra variabili di decisione e la sezione delle azioni mostra le risposte che devono essere prodotte quando le corrispondenti combinazioni di condizioni sono vere. I limiti sono le azioni risultanti e sono determinate dai valori correnti delle variabili di decisione. Le azioni sono indipendenti dall ordine di input e dall ordine in cui le condizioni sono valutate e possono apparire più di una volta, ma ogni combinazione di condizioni è unica. 47

90 IS2 -> Capitolo 3 Le condizioni di uso ideali dove una, tra molte risposte distinte, deve essere selezionata in accordo a casi distinti delle variabili di input. Questi casi possono essere modellati da espressioni booleane mutualmente esclusive sulle variabili di input. La risposta che deve essere prodotta non dipende dall ordine in cui le variabili di input sono definite o valutate. La risposta non dipende da input o output precedenti. Scalabilità: per n condizioni, ci possono essere al più 2 n varianti (combinazioni uniche di condizioni e azioni). Ma per fortuna ci sono di solito molto meno varianti esplicite. I valori Don t care nelle tabelle di decisione aiutano a ridurre il numero di varianti. Don t care possono corrispondere a diversi casi: Gli input sono necessari ma non hanno effetto Gli input possono essere omessi Casi mutualmente esclusivi Metodo dei grafi causa-effetto È una tecnica grafica che aiuta a derivare tabelle di decisione e mira a creare combinazioni interessanti di test data. Inoltre individua cause (condizioni su input, stimoli) e effetti (output, cambiamenti di stato del sistema). Le cause devono essere stabilite in modo tale da essere o vere o false (espressioni booleane). Specifica esplicitamente vincoli (ambientali, esterni) su cause ed effetto e soprattutto aiuta a selezionare i più significativi sottoinsiemi di combinazioni input-output e costruisce tabelle di decisione più piccole. La struttura di un grafo è così costituita: Associazione di un nodo ad ogni causa e ad ogni effetto o Nodi causa ed effetto piazzati su lati opposti di un foglio interconnessione di nodi causa ed effetto e produzione di un grafo booleano o Una linea da una causa ad un effetto indica che la causa è una condizione necessaria per l effetto o Una singola causa può essere necessaria per diversi effetti; un singolo effetto può avere molte cause necessarie o Se un effetto ha due o più cause, la relazione logica tra le cause è espressa inserendo operatori and e or logici tra le due linee o Una causa la cui negazione è necessaria per un effetto è espressa etichettando la linea con l operatore logico not Possono essere usati nodi intermedi per semplificare il grafo e la sua costruzione Esempio (1) Un DBMS organizza i file nel db in modo che il nome di ciascuno di essi sia listato in un indice. L'indice è organizzato in 10 sezioni. Il programma da testare consente all'utente di immettere comandi per ottenere il display di una sezione dell'indice. Requisiti: 48

91 IS2 -> Capitolo 3 Per ottenere il display di una delle 10 sezioni dell'indice, l'utente deve fornire in input un comando costituito da una lettera ed una cifra. La lettera deve essere "D" per Display o "L" per List e deve trovarsi in colonna 1. La cifra deve essere in (0,...,9) e deve trovarsi in colonna 2. A fronte di un comando corretto viene realizzato il display della sezione desiderata. Se il primo carattere è scorretto viene stampato il messaggio A "INVALID COMMAND". Se è scorretto il secondo carattere viene stampato il messaggio B "INVALID INDEX NUMBER". Derivazione di Test Case è la produzione di una tabella delle decisioni: cause sulle righe effetti colonne Per ogni effetto percorrere il grafo alla ricerca delle cause che rendono true l'effetto definendo conseguentemente una colonna della tabella: possibili più colonne per uno stesso effetto. Ad ogni colonna della tabella delle decisioni corrisponde un Test Case. Esempio: gestione clienti di un albergo Dato un cliente, il tipo di stanza occupata ed il periodo di permanenza, il programma calcola il conto. Per le stanze esistono 3 tariffe (corrispondenti al tipo di stanza): singole, doppie e matrimoniali; i clienti che hanno soggiornato meno di 15 giorni pagano tariffa piena; i clienti che hanno soggiornato almeno 15 giorni hanno diritto ad uno sconto del 10%; i clienti di riguardo, infine, hanno diritto ad uno sconto del 30%. Lo sconto del 30% non permette di beneficiare dello sconto del 10%. Le tariffe dipendono dalla stagione: sono considerati alta stagione i fine settimana, il periodo di Natale, dal 24 dicembre al 6 gennaio e il mese di agosto; media stagione i mesi di luglio e settembre; bassa stagione, infine, il resto dell anno. Nel caso di conto inferiore a lire il pagamento può essere fatto solo in contanti; nel caso di conto superiore o uguale a lire il pagamento può essere effettuato in contanti e/o carta di credito; un cliente di riguardo può pagare con assegni o carta di credito e/o contanti. Cause ed effetti: 1.cliente di riguardo 10. permanenza inferiore a 15 giorni 20. stanza singola 21. stanza doppia 22. stanza matrimoniale 30. alta stagione 31. media stagione 32. bassa stagione 49

92 IS2 -> Capitolo conto totale inferiore a lire 60. nessuno sconto 61. sconto del 30% 62. sconto del 10% 90. pagamento in contanti 91. pagamento con carta di credito 92. pagamento con assegni NOTA: IL PROF. DISSE A LEZIONE CHE NELLA TABELLA C ERANO ERRORI PERCHE C ERANO ERRORI NELLA TRACCIA BEL PROBLEMA DI ESEMPIO SUL FATTO DELLE PERCENTUALI DA APPLICARE CHI USUFRUISCE DEL 10% DI SCONTO NON HA QUELLO DEL 30% ECC DA VEDERE TEST CATEGORY PARTITION (TRATTO DAL DOCUMENTO: THE CATEGORY- PARTITION METHOD FOR SPECIFYING AND GENERATING FUNCTIONAL TESTS) Molte specifiche software ancora sono scritte nel linguaggio naturale. Questi documenti sono discorsivi e non strutturati, e sono cosi difficili da usare come una base per un collaudo funzionale. Il tester deve trasformare tale specifica in modo preciso e strutturato in modo da poter avere una rappresentazione intermedia dalla quale possono essere generati test case. Il processo di trasformare la specifica di un linguaggio naturale in una rappresentazione intermedia può essere utile per rivelare problemi quali ambiguità, contraddizioni, etc. Il tester deve frequentemente, chiedere all analista di chiarificare l'intenzione di una particolare sezione o frase. Queste domande vengono fatte per mettere in mostra errori nella specifica prima che del codice sia scritto o nella realizzazione prima che alcun codice sia eseguito. Il primo passo del metodo di category-partition è aiutare a portare a termine la fase di specificazione e analisi. I passi successivi riguardano informazioni per rilevare errori. Il metodo comincia con la decomposizione della specifica funzionale in unità funzionali che possono essere esaminate indipendentemente. Un'unità funzionale può essere un comando utente ad alto livello o una funzione descritta nelle specifiche che sono chiamate da altre funzioni del sistema. Per un requisito funzionale molto grande, questa decomposizione identifica delle subcomponent che possono essere eseguite indipendentemente. La decomposizione indipendente è estremamente raccomandata ogni volta che vi è una scoperta di un errore, in quanto aumenta le opportunità di trovare problemi nella specifica, ed aumenta le opportunità che la decomposizione risultante conduca a prove più efficienti per il software. Dopo che le unità funzionali di un subcomponent sono state identificate, il passo successivo di una decomposizione è di identificare i parametri e le condizioni di ambiente che colpiscono il comportamento di esecuzione della funzione. I parametri sono i contributi espliciti ad un'unità funzionale, approvvigionata dall'utente o un altro programma. Le condizioni di ambiente sono caratteristiche dello stato del sistema 50

93 IS2 -> Capitolo 3 dovute all esecuzione e alla durata di un'unità funzionale. I test case attuali per un'unità funzionale sono composti da specifici valori dei parametri scelti per massimizzare le opportunità di trovare errori nella realizzazione della funzionalità. Per selezionare questi valori, il prossimo passo nel processo di decomposizione è quello di trovare le categorie di informazioni che caratterizzano ogni parametro e le condizioni d ambiente. Per ogni parametro o condizione d ambiente, il tester contrassegna le frasi nella specifica che descrive come una unità funzionale si comporta nel rispettare le stesse caratteristiche di un parametro o condizione d ambiente. Ogni caratteristica che può essere identificata in questo modo, viene registrata come una categoria nuova. Descrizioni ambigue, contraddittore, o mancanti del comportamento di una funzione sono scoperti frequentemente durante questo processo. Il passo successivo della decomposizione è di suddividere ogni categoria in distinct choices che include tutti i generi diversi di valori possibili per la categoria. Ogni scelta all'interno di una categoria è un set di valori simili che può essere presunto dal tipo di informazioni presenti nella categoria. Le scelte sono classi di partizioni i quali rappresentano gli elementi che saranno utilizzati per costruire i test case. Mentre le categorie sono derivate completamente dalle informazioni nella specifica, le scelte possono essere basate sulla specifica, sulla passata esperienza del tester nella selezione dei test case validi e sulla conoscenza degli errori che possono accadere in situazioni simili. Se il codice è disponibile per l'analisi, il tester può basare le proprie scelte anche sulla struttura interna del programma. Una volta che le categorie sono state stabilite e le scelte effettuate, vengono scritte in un documento formale chiamato test specification per ogni unità funzionale. Il test specification consiste in un elenco delle categorie e delle scelte per ogni categoria. Le informazioni della specifica sono usate per produrre un insieme di test frames che sono la base per la costruzione dei reali test case. Un test frame consiste di un insieme di scelte dalla specifica, con ogni categoria che offre zero o una scelta. Un caso di prova reale è costruito da un test frame specificando un solo elemento da ognuno delle scelte nel frame. Un test specification come descritto finora è una specifica di unrestricted, in quanto non ci sono relazioni indicate fra alcune delle sue scelte. Ogni frame generato da unrestricted specification contiene precisamente una scelta da ogni categoria. Il numero totale di frame generato da una unrestricted specification è uguale al prodotto del numero di scelte in ogni categoria. Siccome le scelte in categorie diverse frequentemente interagiscono l'un l'altro in modo che esaminano i risultati, le scelte in una specifica di prova possono essere annotate in modo che indichino queste relazioni. Una costrizione tipica dice che una scelta da un category non può accadere insieme in un frame di prova con certe scelte dalle altre categorie. Le costrizioni sono usate per raffinare il set senza restrizioni nei frame di prova. L estremo di un set di casi di prova è una decisione prammatica che dipende dalle finalità del software, il bilancio del progetto e forse, sui livelli di abilità dei programmatori ed i tester. Dopo che una specifica di prova è stata annotata con costrizioni, un generatore produce i limiti di prova. Grazie a questa combinazione particolareggiata di informazioni creata da un processo automatizzato, è relativamente facile per l'utente fare cambi alla specifica di prova, per poi rieseguire il processo per produrre un set di nuovi limiti per la prova. I passi finali nel processo di produzione di prova sono quelli di trasformare i limiti di prova generati nei casi di prova, e poi combinare i casi in scritture di prova. Una scrittura di prova è una sequenza di casi di prova relativi ad uno o più unità funzionali. Il tester deve decidere se i casi di prova in una scrittura dovrebbero essere indipendenti. I passi di metodo di categoria-sezione sono come segue: A. Analisi della specifica. Il tester identifica unità funzionali ed individuali che possono essere esaminate. Per ogni unità, il tester identifica: 1. parametri dell'unità funzionale; 2. caratteristiche di ogni parametro; 51

94 IS2 -> Capitolo 3 3. oggetti nell'ambiente il cui stato potrebbe colpire l'operazione dell'unità funzionale; 4. caratteristiche di ogni oggetto di ambiente. Il tester classifica poi questi articoli in categories che hanno effetto sul comportamento dell'unità funzionale. B. Suddividere le categorie in scelte. Il tester determina i casi significativi e diversi che possono accadere all'interno di ogni categoria di parametro / ambiente. C. Determinare le costrizioni fra le scelte. Il tester decide come le scelte interagiscono, come l'avvenimento di una scelta può colpire l'esistenza di un altra, e come le restrizioni speciali possano incidere su una qualsiasi scelta. Questo step ed i due passi seguenti frequentemente compaiono ripetutamente come il tester raffina la specifica di prova per realizzare il livello desiderato di prove funzionali. D. Scrivere e processare test di specifica La categoria, le scelte, e le informazioni di costrizione sono scritti in un documento formale chiamato Test Specification. Le specifiche scritte sono successivamente processate dal generatore che produce un set limite di prova per l unità funzionale. E. Valutare il risultato del generatore Il tester esamina i frame di prova prodotte dal generatore, e determina se alcuni cambi del test specification sono necessari. Le ragioni per cambiare il test di specifica include l'assenza di alcune situazioni di test evidentemente necessarie, la comparsa di combinazioni di test impossibili, o un giudizio per i numerosi casi di prova che sono stati prodotti. Se la specifica deve essere cambiata, il passo D è ripetuto. F. Trasformare in test script Quando la prova di specifica è stabile, il tester converte i frame del test prodotte dal tool in casi di prova, e organizza i casi di prova in test script. TESTING STRUTTURALE 1 SOFTWARE TESTING: WHITE BOX Sebbene non siano semplici da utilizzare, queste tecniche possono essere adottate per andare a vedere se i casi di test che ho selezionato in altri modi (es. random, criteri black box) riescono a coprire determinati oggetti. La definizione dei casi di test e dell'oracolo è fondata sulla base della conoscenza della struttura del software ed in particolare del suo codice, analizzare i tipi e strutture di dati (classi), strutture di controllo, flusso di controllo, flusso dei dati, etc. Non è scalabile (usato soprattutto a livello di unità o sottosistema). È una attività complementare al testing funzionale. Non può rilevare difetti che dipendono dalla mancata implementazione di alcune parti della specifica. Si decide quali sono le parti del programma che devo realmente eseguire e cercare di coprire un insieme in modo tale che l unione di questi cammini mi testa tutta la procedura. In generale la definizione dei casi di test e dell'oracolo è fondata sull'adozione di criteri di copertura degli oggetti che compongono la struttura dei programmi. COPERTURA: definizione di un insieme di casi di test in modo tale che gli oggetti di una definita classe (es. strutture di controllo, istruzioni, archi del GFC, predicati,..etc.) siano attivati almeno una volta nell'esecuzione dei casi di test. 1 (wikipedia) Il test strutturale, detto anche white box, è un particolare tipo di test che viene effettuato per rilevare errori in una o più componenti (parte di codice, metodo, funzione, classe, programmi, ecc.) di un sistema software. Il suo funzionamento si basa su alcuni criteri che hanno lo scopo di trovare dati di test che consentano di percorrere tutto il programma. Per trovare un errore nel codice, infatti, bisogna usare dei dati che percorrono la parte erronea del programma. Per testare una parte di programma si introduce il concetto di cammino: una sequenza di istruzioni attraversata durante un esecuzione. Naturalmente non esiste un criterio in grado di testare ogni singolo cammino dato l'elevato numero di questi ultimi (soprattutto in presenza di cicli), tuttavia, è possibile trovare un numero finito di cammini indipendenti che combinati tra loro forniscano tutti (o per lo meno la maggior parte) i restanti cammini. Se eseguito correttamente e senza particolari eccezioni, il test può coprire fino al 90% delle istruzioni. 52

95 IS2 -> Capitolo 3 Definizione di una metrica di copertura: Test Effectiveness Ratio (TER) = # oggetti coperti / # oggetti totale NB: Copertura totale (100%) non sempre possibile (problemi di indecidibilità). Selezione di casi di test Come selezionare i casi di test per il criterio di copertura adottato? Ogni caso di test corrisponde all esecuzione di un particolare cammino sul GFC di un programma P; devono essere individuati i cammini che ci garantiscono il livello di copertura desiderato; l esecuzione simbolica di ogni cammino e individuazione di input data che soddisfano la path condition (e che causano l esecuzione del cammino) si rivela una strategia troppo costosa. Una strategia meno costosa è quella di scegliere un criterio di copertura funzionale e un criterio di copertura strutturale (complementari tra di loro): o bisogna individuare i casi di prova in accordo al criterio di copertura funzionale ed eseguire il test funzionale; o bisogna controllare la copertura rispetto al criterio di copertura strutturale scelto ed individuare le parti non coperte e selezionare dei cammini che ci consentono di raggiungere il livello di copertura desiderato. o individuare i casi di prova per l esecuzione di questi cammini ed eseguire il test strutturale. Posso usare delle euristiche oppure cercare di capire i cammini che ho eseguito e le variazioni dei cammini che posso fare per rendere un test case completo. Una parte dei cammini con la tecnica black box già li ho e quelli che mancano li possono integrare con la tecnica white box. Copertura dei nodi (statement) Dato un programma P, viene definito un insieme di test case la cui esecuzione implica l'attraversamento di tutti i nodi di GFC(P), ovvero l'esecuzione di tutte le istruzioni di P. Test Effectiveness Ratio (TER) Esempio Node Coverage: Copertura delle decisioni (branch) Un branch identifica un percorso di sviluppo concomitante che richiede gestione di configurazione indipendente. Lo sviluppo di caratteristiche diverse può essere fatto concomitantemente e, comunque, in un secondo tempo, i diversi gruppi di sviluppo possono unirle in una sola versione. La sequenza della versione creata da ogni squadra è un branch che è indipendente dalla versione creata dall'altra squadra. Quando delle versioni differenti di branch hanno bisogno di essere unite viene fatto un unione. 53

96 IS2 -> Capitolo 3 Dato un programma P, viene definito un insieme di test case la cui esecuzione implica l'attraversamento di tutti i rami di GFC(P), ovvero l'esecuzione di tutte le decisioni di P. Test Effectiveness Ratio (TER) NB: la copertura delle decisioni implica la copertura dei nodi Copertura di decisioni e condizioni Dato un programma P, viene definito un insieme di test case la cui esecuzione implica l'esecuzione di tutte le decisioni e di tutte le condizioni caratterizzanti le decisioni in P. Tali criteri implicano sia copertura delle condizioni che copertura delle decisioni. La copertura di tutte le condizioni NON implica la copertura di tutte le decisioni! Multiple condition coverage: la copertura di tutte le combinazioni di condizioni implica la copertura di tutte le condizioni e di tutte le decisioni Modified condition coverage: per ogni condizione vengono considerate solo le combinazioni di valori per le quali una delle condizioni determina il valore di verità della decisione. Questa fase è molto costosa e bisogna effettuare una riduzione dei casi di test. Copertura dei cammini Dato un programma P, viene definito un insieme di test case la cui esecuzione implica l'attraversamento di tutti i cammini di GFC(P) 54

97 IS2 -> Capitolo 3 Test Effectiveness Ratio (TER) Problemi: numero di cammini infinito (o comunque elevato) e infeasible path Copertura dei cammini: soluzioni Un numero di cammini infinito implica la presenza di circuiti. NB: il numero dei cammini elementari (privi di circuiti) in un grafo è finito. Soluzione: limitare l insieme dei cammini. Criterio di n-copertura dei cicli o Si seleziona un insieme di test case che garantisce l esecuzione dei cammini contenenti un numero di iterazioni di ogni ciclo non superiore ad n (ogni ciclo deve essere eseguito da 0 ad n volte). Al crescere di n può diventare molto costoso Caso pratico n = 2 (ogni ciclo viene eseguito 0 volte, 1 volta, 2 volte). NB: il criterio di 1- copertura dei cicli (n = 1) implica il criterio di copertura dei branch Metodi basati su exemplar-paths L'insieme dei cammini del grafo di controllo viene partizionato in un numero finito di classi di equivalenza. Il criterio di copertura: un insieme di test case che assicuri l'attraversamento almeno una volta di almeno un cammino per ogni classe. Metodo degli exemplar path (cammini esemplari). Due cammini sono 55

98 IS2 -> Capitolo 3 assegnati alla stessa classe se essi differiscono unicamente nel numero di volte per il quale un circuito sul cammino viene percorso. Un cammino privo di circuiti è detto cammino elementare. Il numero di cammini elementari in un grafo è finito. Metodi dei cammini linearmente indipendenti (Mc Cabe) o Siano n N ed n E il numero di nodi e archi di un grafo del flusso di controllo G; un cammino può essere rappresentato come un vettore di n E elementi (uno per ogni arco); ogni elemento rappresenta il numero di occorrenze (0 o più) dell arco sul cammino. Possibile stabilire se un insieme di cammini sono linearmente indipendenti: un cammino è linearmente indipendente rispetto ad un insieme di cammini se attraversa almeno un arco non ancora percorso. NB: Il metodo di McCabe implica il criterio di copertura dei branch. Un insieme massimale di cammini linearmente indipendenti è detto insieme di cammini di base se 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. Criterio di copertura: un insieme di test case che garantisce l esecuzione almeno una volta di ogni cammino in un insieme di cammini di base. Il numero dei cammini linearmente indipendenti di un programma è pari al numero ciclomatico 2 di McCabe. Sia G il grafo del flusso di controllo di un programma Numero di archi di G: ne Numero di nodi di G: nn Numero di nodi predicato (decisioni) di G: nd Il numero di predicati in G è pari al numero di regioni chiuse in G Metrica di McCabe (numero ciclomatico): v(g) = ne - nn + 2 = nd + 1 N.B. Per nodi predicati in vie, ogni nodo vale come n-1 nodi a due vie. 2 Complessità Ciclomatica (Metrica Di McCabe) detto anche numero ciclomatico, è una metrica strutturale relativa al flusso di controllo di un programma e rappresenta la sua complessità logica cioè lo sforzo per realizzarlo e comprenderlo. La formula per calcolare la complessità ciclomatica (V(G)) è: V(G) = P + 1 con P numero di predicati, cioè le strutture di controllo presenti nel programma (if, while, for). Se abbiamo un programma rappresentato sotto forma di diagramma di flusso (Control Flow Graph) è possibile calcolare il numero ciclomatico in questo modo: V(G) = E N + 2 con E numero di archi, N numero di nodi. Il numero ciclomatico rappresenta il numero di cammini linearmente indipendenti cioè che introducono almeno un nuovo insieme di istruzioni o una nuova condizione rispetto ad un altro. È utile conoscere questo parametro in fase di test strutturale o per il calcolo della complessità di una classe valutata in base alla complessità dei singoli metodi presenti in essa. 56

99 IS2 -> Capitolo 3 Ricerca dei percorsi indipendenti Metodi basati su data-flow o Dato un programma P, sia GFC(P) il suo grafo del flusso di controllo. Un cammino x, n1, n2,..., nk, y dal nodo x al nodo y è un definition clear path (cammino libero da definizioni) rispetto ad una variabile v, se non ci sono definizioni di v su nessuno dei nodi ni (NB: possono esserci definizioni di v sui nodi x e y) un definition clear path x, n1, n2,..., nk, y dal nodo x al nodo y è un definition-use path (du-path in breve) rispetto ad una variabile v, se c e una definizione di v ad x ed un uso di v ad y Gcov gcov è uno strumento per il controllo della copertura di branch e statements in un programma c. Più in generale, può essere considerato uno strumento di code profiling. Quanto spesso una linea di codice è stata eseguita. Quale il tempo di esecuzione (cumulativo) per quella linea. Esso è fornito a corredo del compilatore gcc. Compilare il programma utilizzando lo switch -fprofile-arcs -ftest-coverage di gcc gcc -fprofile-arcs -ftest-coverage myprog.c o myprog Eseguire il programma passandogli in input i casi di test prodotti./myprog Eseguire gcov gcov myprog.c 90.91% of 11 source lines executed in file myprog.c Creating myprog.c.gcov. Esaminare l output contenuto in myprog.c.gcov gcov branch coverage Quanto visto in precedenza è utile per esaminare lo statement coverage. Per visualizzare il branch coverage, occorre eseguire gcov con l opzione b 57

100 IS2 -> Capitolo 3 gcov -b myprog.c 90.91% of 11 source lines executed in file myprog.c 83.33% of 6 branches executed in file myprog.c 66.67% of 6 branches taken at least once in file myprog.c 80.00% of 5 calls executed in file myprog.c Creating myprog.c.gcov. #include<stdio.h> int main() 1 { call 0 returns = 100% 1 int j,x,y,out; 1 out=0; 1 scanf("%d",&x); call 0 returns = 100% 1 scanf("%d",&y); call 0 returns = 100% 4 for(j=0;j<x;j++) branch 0 taken = 75% branch 1 taken = 100% branch 2 taken = 100% { 3 if(j>y) branch 0 taken = 100% ###### printf("inside\n"); call 0 never executed branch 1 never executed else 3 out++; } 1 if(out) branch 0 taken = 0% 1 printf("out is %d\n",out); call 0 returns = 100% } Il conteggio delle esecuzioni e il computo della copertura è cumulativo rispetto alle successive esecuzioni del programma. Le tracce di esecuzione sono mantenute nel file.da. Metodi basati sul data-flow Il Data-Flow model è una strada intuitiva di visualizzare come i dati sono processati dal sistema. Esso rappresenta il processo come un insieme di attività, ognuna delle quali modifica qualche dato dell informazione. Mostra in che modo gli input del processo (come le specifiche) si trasformano in output (con la progettazione). Le attività rappresentano le trasformazioni operate da persone o computer. A livello di analisi dovrebbero essere utilizzati per modellare il modo in cui i dati sono elaborati dal sistema esistente. Un esempio del loro uso è quello di mostrare come i dati attraversano una sequenza di fasi di elaborazione, dove al passaggio ad ogni fase comporta la trasformazione dei dati. I modelli a flusso di dati sono importanti perché seguire e documentare come i dati sono associati a processi particolari some si muovono nel sistema, aiuta gli analisti a capire cosa sta succedendo. [Vedi metodi basati su data-flow] Uso di una variabile v: Computational use di v o c-use(v) si ha quando la variabile v è usata in espressioni per il calcolo di un altra variabile o in istruzioni di output. Predicate use di v o p-use(v) si ha quando la variabile v è usata in un predicato che condiziona il flusso di controllo. Devo selezionare cammini da eseguire e che coprano un du-path. Ogni cammino che vado a selezionare 58

101 IS2 -> Capitolo 3 coprirà una serie di du-path. Bisogna partizionare i du-path in classi e selezionare la classe desiderata. all-du-path: deve essere eseguito ogni du-path da ogni definizione ad ogni uso all-uses: deve essere eseguito almeno un du-path da ogni definizione ad ogni uso all-p-uses/some-c-uses: deve essere eseguito almeno un du-path da ogni definizione ad ogni p-use; se una definizione non raggiunge nessun p-use, allora va eseguito almeno un du-path dalla definizione ad un c-use all-c-uses/some p-uses: deve essere eseguito almeno un du-path da ogni definizione ad ogni c-use; se una definizione non raggiunge nessun c-use, alloora va eseguito almeno un du-path dalla definizione ad un p-use all-c-uses: deve essere eseguito almeno un du-path da ogni definizione ad ogni suo c-use all-p-uses: deve essere eseguito almeno un du-path da ogni definizione ad ogni suo p-use all-defs: almeno un du-path da ogni definizione ad un suo uso (p-use o c-use) Criteri strutturali e livelli di testing La selezione di casi di test mediante criteri di copertura strutturali è principalmente indicata per il testing di unità. Criteri basati sui cammini sono poco scalabili. Nella pratica, test selezionati con criteri funzionali vengono usati per controllare su scala più larga livelli di copertura strutturale principalmente statement e branch coverage. 59

102 IS2 -> Capitolo 3 TESTING DI SOFTWARE O.O. Il testing di sistemi software OO deve tener conto delle seguenti caratteristiche: sempre: o Astrazione sui dati (stato e information hiding) o Ereditarietà o Polimorfismo o Binding dinamico spesso: o Genericità o Gestione delle eccezioni o Concorrenza Nuovi livelli di test il concetto di classe come dati più operazioni cambia il concetto di unità il test di integrazione di oggetti è diverso dal test di integrazione tradizionale Nuova infrastruttura: driver e stub devono considerare lo stato (information hiding) Nuovi oracoli: lo stato non può essere ispezionato con tecniche tradizionali Nuove tecniche di generazione dei casi di test. In un sistema Object-oriented, possono essere identificati 4 livelli di testing: testare le operazioni individuali associate all oggetto: queste sono funzioni o procedure e sono usati gli approcci white-box e black-box. Testing individual object classes: i principi del testing black-box non cambiano ma la notazione di classe di equivalenza deve essere estesa per coprire le relative operazioni di sequenza. Similmente, il testing strutturale richiede tipi differenti di analisi (ne parleremo in object class testing). Testing cluster of object: un rigida integrazione top-down o bottom-up è inappropriata per creare gruppi di oggetti relativi. Testing the object-oriented system Object class testing: Quando testiamo l oggetto, il test di prova completa dovrebbe includere: Il testing isolato per tutte le operazioni associate all oggetto; Il settaggio e l interrogazione per tutti gli attributi associati con l oggetto; L esercizio dell oggetto in tutti gli stati possibili. Questo vuol dire che ogni evento che provoca un cambio nello stato dell oggetto dovrebbe essere testato. Quando una superclasse prevedere operazioni riguardanti anche un numero di sottoclassi, tutte le sottoclassi che ereditano devono essere testate per le operazioni interessate. Le ragioni a questo è che le operazioni ereditate possono creare assunzioni riguardanti altre operazioni e attributi e questi potrebbero cambiare per effetto dell ereditarietà. Ugualmente, quando le operazioni della superclasse sono sovrascritte, le operazioni sovrascritte devono essere testate. La notazione di classe di equivalenza può essere applicata alle classes object. Quindi, le classi di equivalenza devono essere identificate quando inizializzate, accesso e aggiornamento a tutti gli attributi della object class. Stato e information hiding Linguaggi procedurali standard: componente base: procedura; metodo di test: test della procedura basato su input/output Linguaggi object-oriented: componente base: Classe = struttura dati + insieme di operazioni; oggetti sono istanze di classi; la correttezza non è legata solo all output, ma anche allo stato, definito dalla struttura dati; 60

103 IS2 -> Capitolo 3 lo stato privato può essere osservato solo utilizzando metodi pubblici della classe (e quindi affidandosi a codice sotto test). Metodi per la generazione di casi di test Il test del singolo metodo può essere fatto con tecniche tradizionali (metodo=procedura). MA: metodi più semplici di procedure; scaffolding più complicato; (esempio Push: scaffolding = coda); ATTENZIONE: con l ereditarietà lo stesso metodo può venire usato più volte in contesti diversi. Può convenire fare il test dei metodi durante il test delle classi, ma non si può ignorare il test del singolo metodo! Scaffolding L infrastruttura deve settare opportunamente lo stato per poter eseguire i test (driver); esaminare lo stato per poter stabilire l esito dei test (oracoli) ma lo stato è privato Approcci intrusivi: modificare il codice sorgente; aggiungere un metodo test driver alla classe, usare costrutti del linguaggio (esempio: costrutto friend) Oracolo Usare scenari (sequenze di invocazioni) equivalenti. Esempio Coda: equivalenti SEQ1=create,add(5),add(3),delete SEQ2 = create,add(3) Oppure non equivalenti SEQ1 = create,add(5),add(3),delete SEQ2 = create,add(5) In assenza di specifiche formali: sequenze definite dall utente in base a conoscenza della classe (specifica informale). Stato e generazione dei casi di test La tecnica: costruire una macchina a stati finiti: stati = insieme di stati della classe; transizioni = invocazione di metodi. Percorrere la macchina a stati finiti per derivare casi di test. Approcci: basato su specifiche, basato su codice (tramite il white box). I cammini di una macchina a stati finiti corrispondono a sequenze di esecuzione. Un insieme di test ragionevole può essere ottenuto selezionando tutti i cammini senza cicli. Casi di test = cammini della macchina a stati finiti. Diverse tecniche per generare macchine a stati finti e limitare numero di cammini che corrispondono a diversi insiemi di test generati. Generazione dei casi di test per una macchina a stati gerarchica con lo stesso principio. E necessario ricordare che una transizione in ingresso (uscita) a (da) uno stato decomposto in OR può rappresentare più transizioni elementari che richiedono test distinti; uno stato decomposto in AND rappresenta il prodotto cartesiano degli stati componenti. Tutte le possibili coppie devono essere testate. 61

104 IS2 -> Capitolo 3 Criterio di copertura La macchina a stati finiti può essere utilizzata per identificare criteri di copertura strutturale: generare sequenze di test a partire da specifiche, generare macchina a stati finiti a partire da codice, la qualità del test generato (o del software) può essere misurata dal grado di copertura dei cammini della macchina a stati finiti. Problemi rilevabili Esistono cammini che non corrispondono a test funzionali: cattiva definizione dei test funzionali (ho dimenticato casi significativi), cattiva specifica (mancano casi significativi), cattiva implementazione (casi non richiesti), esistono test funzionali che non corrispondono a cammini, cattiva implementazione (missing paths). Testing di integrazione Né top-down né bottom-up integration sono realmente appropriata per sistemi orientati agli oggetti. Ci sono tre possibili approcci di test di integrazione che possono esser usati: 1. Use-case o scenario-based testing: descrive un modo di usare il sistema. Il testing può basarsi sulla descrizione dello scenario e dell oggetto cluster creato che supporta gli use-case che riguardano il modo di uso. 2. Thread testing: è basato sulla risposta del sistema su un particolare input o un set di eventi di input. I sistemi object oriented sono spesso event-driven cosi questo è particolarmente appropriato per il tipo di testing da usare. Per usare questo approccio bisogna identificare quali processi di eventi threads attraversano il sistema. 3. Object interaction testing: l approccio relativo al testing group per l interazione di oggetti è proposto da Jorgensen ed Erickson. Loro proposero un livello intermediario di test di integrazione che può essere basato per identificare il percorso dei method-message. Queste sono tracce che attraverso una sequenza di iterazioni di oggetti che si ferma quando un object operation non può essere chiamata dai servizi di qualsiasi altro oggetto. Big bang: in generale poco adatto Top-down e bottom-up: cambia il tipo di dipendenze tra moduli o Dipendenze: uso di classi ed ereditarietà Se A usa B allora A dipende da B Se A eredita da B allora A dipende da B o Preferibile una strategia bottom-up (testare prima le classi indipendenti) o Stub troppo difficili da costruire o Threads: un thread è identificato con una sequenza di messaggi Testing di integrazione basato su threads La generazione dei casi di test può essere effettuata a partire dai diagrammi di interazione (specifiche). Opportuno costruire threads anche dal codice e verificare la corrispondenza con le specifiche. Problemi: ereditarietà, polimorfismo e binding dinamico. Generazione di casi di test a partire da diagrammi di interazione I diagrammi di interazione indicano possibili sequenze di messaggi. Dovrebbero indicare i casi frequenti e quelli particolari. Selezione immediata: generare un test per ogni diagramma di interazione. Selezione più accurata: per ogni diagramma individuare possibili alternative e per ogni alternativa selezionare un ulteriore insieme di casi di test. 62

105 IS2 -> Capitolo 3 Problemi di Integrazione: Ereditarietà Linguaggi procedurali classici: il codice è strutturato in procedure (che possono essere contenute in moduli); una volta eseguito il test di modulo di una procedura non è necessario rieseguirlo (salvo modifiche). Linguaggi orientati a oggetti: il codice è strutturato in classi; l'ereditarietà è una relazione fondamentale tra classi; nelle relazioni di ereditarietà alcune operazioni restano invariate nella sotto-classe, altre sono ridefinite, altre aggiunte (o eliminate). Ereditarietà: Problemi È necessario identificare le proprietà che devo ritestare: operazioni aggiunte, operazioni ridefinite, operazioni invariate, ma influenzate dal nuovo contesto. Può essere necessario verificare la compatibilità di comportamento tra metodi omonimi in una relazione classe-sottoclasse tramite riuso test, test specifici. Ereditarietà: Possibili soluzioni Appiattimento delle classi: ritesto ogni metodo ignorandone la gerarchia di appartenenza: vantaggioso per il riuso dei test, altamente ridondante e quindi inefficiente. Test incrementale: tassonomia dei tipi di proprietà in una classe derivata, storie di test, possibile identificare quali proprietà ritestare, quali test riusare e quali proprietà necessitino di nuovi test, molto più efficiente dell appiattimento, ma più costosa; basato sul caso pessimo. Polimorfismo e binding dinamico Linguaggi procedurali classici: le chiamate a procedura sono associate staticamente al codice corrispondente. Linguaggi orientati a oggetti: un riferimento (variabile) può denotare oggetti appartenenti a diverse classi in relazione tipo-sottotipo (polimorfismo), ovvero il tipo dinamico e il tipo statico dell oggetto possono essere differenti: più implementazioni di una stessa operazione 63

106 IS2 -> Capitolo 3 il codice effettivamente eseguito è identificato a run-time, in base alla classe di appartenenza dell oggetto (binding dinamico) Es. Polimorfismo e binding dinamico: problemi Il test strutturale può diventare non praticabile; Come definisco la copertura in un invocazione su un oggetto polimorfo? Come creo test per coprire tutte le possibili chiamate di un metodo in presenza di binding dinamico? Come gestisco i parametri polimorfi? Il test esaustivo può diventare impraticabile Esplosione combinatoria dei possibili casi Definizione di un criterio di selezione specifico: selezione casuale basata sull analisi del codice: individuazioni di combinazioni critiche, data flow analysis. Altri problemi: Genericità Le classi parametriche devono essere instanziate per poter essere testate. Che ipotesi posso e devo fare sui parametri? Servono classi fidate da utilizzare come parametri (un tipo di stub particolare). Quale metodo devo seguire quando faccio il test di un componente generico che riuso? Altri problemi: Gestione delle eccezioni Le eccezioni modificano il flusso di controllo senza la presenza di un esplicito costrutto di tipo test and Branch. Problemi nel calcolare gli indici di copertura della parte di codice relativa alle eccezioni; copertura ottimale: sollevare tutte le possibili eccezioni in tutti i punti del codice in cui è possibile farlo (può non essere praticabile); copertura minima: sollevare almeno una volta ogni eccezione. Altri problemi: Concorrenza Problema principale: non-determinismo: risultati non-deterministici; esecuzione non-deterministica. Casi di test composti da valori di input e output sono poco significativi. Casi di test composti da valori di input/output e da una sequenza di eventi di sincronizzazione (occorre però forzare lo scheduler a seguire una data sequenza). SLICING Slicing un po di storia.. Con lo sviluppo di sistemi software sempre più complessi ed eterogenei risulta necessario trovare delle tecniche di ausilio alla tradizionale attività di debugging e testing. La tecnica denominata slicing si pone proprio in quest ottica poiché nasce dall osservazione che naturalmente i programmatori, durante queste fasi, cercano in qualche modo di astrarre parti del codice sotto analisi rispetto ad un particolare punto di interesse. Una program slice è una fetta del programma principale che potenzialmente determina il valore di un insieme di variabili in un particolare punto del codice sorgente. Dalla definizione originale di 64

107 IS2 -> Capitolo 3 Mark Weiser (1979) sono state presentate molte implementazioni e tecniche ausiliarie per superare i problemi intrinseci nell analisi statica del codice sorgente al fine di ottenere una slice che risultasse corretta rispetto ad alcuni parametri e nello stesso tempo contenente solamente le istruzioni realmente influenti. In questo articolo vogliamo quindi riassumere lo stato dell arte a cui la ricerca è giunta in questi anni, accennando alla reale applicabilità del metodo in contesti produttivi. Introduzione Decomporre i programmi in moduli è sempre stato un approccio molto usato per far fronte alla crescente complessità dei sistemi. Anche la massima Divide et Impera è diventata, con la programmazione Object Oriented, un rimedio efficace per lo sviluppo e la manutenzione di software, evidenziando come sia importante focalizzare la propria attenzione su singoli componenti al fine di realizzare un buon prodotto. Ricavare una slice significa infatti ridurre un programma alle sole istruzioni che influenzano il valore delle variabili in un determinato punto del codice che viene denominato slicing criterion. Lo slicing criterion, nella sua accezione più classica, è definito come una coppia di valori (numero di linea, variabile). Lo slicing è quella tecnica in grado di realizzare in maniera automatica questa riduzione del codice sorgente iniziale, evidenziando solamente le istruzioni che contribuiscono alla determinazione del valore di una specifica variabile. Consideriamo come esempio il semplice frammento di codice: Effettuando ora lo slicing con il seguente criterio (10,product), quello che vogliamo ottenere è un programma ridotto e coerente rispetto al punto di interesse considerato. La slice sarà quindi costituita dalle istruzioni presenti nel programma precedente ad esclusione delle istruzioni alle righe 3, 6 e 9. Nel 1979, Mark Weiser durante la sua tesi di PHD presso l università del Maryland compì degli esperimenti su un gruppo eterogeneo di sviluppatori facendo loro effettuare un intensa attività di debugging su differenti programmi. Successivamente i soggetti vennero sottoposti ad un questionario in cui erano riportati singoli blocchi di istruzioni e in cui dovevano esprimere un giudizio sulla attinenza rispetto agli algoritmi appena analizzati. Da questo semplice esperimento, Weiser confermò quanto di fatto era già stato notato in uno studio precedente: tutte le persone, effettuando analisi correttiva sul codice sorgente di un programma, creano una rappresentazione mentale degli algoritmi, la quale li aiuta a comprendere meglio il meccanismo di funzionamento e a individuare il punto di fault; automatizzando questo processo si potrebbe quindi agevolare ulteriormente il programmatore. Dalla definizione originale di questa tecnica ad opera di Weiser, basata unicamente sull analisi statica del codice sorgente (static slicing), sono state poi intraprese numerose ricerche per superare i limiti di questo metodo. Da un lato si è cercato di superare le problematiche inerenti a particolari costrutti dei linguaggi di programmazione come procedure, operatori di controllo del flusso, puntatori, array, etc. estendendo inoltre la tecnica ai nuovi approcci basati sul paradigma ad oggetti grazie alla definizione di nuovi algoritmi o di tecniche semplificative; dall altro invece si è cercato di utilizzare ulteriori informazioni sull input di una particolare esecuzione al fine di ridurre dinamicamente il codice (dynamic slicing) per poi effettuare considerazioni analoghe al caso statico. Definizione di slice (Weiser) S è un sottoinsieme eseguibile di P S preserva il comportamento di P rispetto al criterio di slicing (p,v) 65

108 IS2 -> Capitolo 3 Lo stato prima dell esecuzione dell istruzione p ristretto alle variabili in V è lo stesso per le esecuzioni sia di P che di S. Contesto applicativo Come è possibile intuire il program slicing è in grado di assistere gli sviluppatori in numerose attività che vanno ben oltre il debugging del codice sorgente. In questo paragrafo cercheremo di analizzare altri possibili utilizzi del program slicing nelle numerose fasi di sviluppo di un programma. Program Differencing: spesso i programmatori si trovano di fronte al problema di rilevare le differenze tra due programmi. Esistono molte tecniche per trovare le differenze testuali tra programmi, ma queste spesso risultano insufficienti. Il program slicing può essere utilizzato per identificare le differenze semantiche tra due programmi. Sarà infatti sufficiente applicare un algoritmo di slicing basato su grafi delle dipendenze per portare alla luce le differenze tra i due programmi che vanno ad influenzare una data istruzione. Program Integration: dati un programma Base e due sue varianti A e B, ottenute modificando copie diverse di Base, lo scopo dell integrazione di programmi è quello di determinare se le modifiche interferiscano tra loro e, se non lo fanno, di creare un programma integrato che incorpora le modifiche di entrambe le versioni A e B, insieme alle parti di Base che non sono state modificate da nessuna delle due. Per procedere all integrazione è necessario per prima cosa identificare le modifiche apportate dalle due varianti usando il Program Differencing discusso in precedenza. La parte di Base da preservare è ottenuta identificando le porzioni isomorfe delle slice tra A, B e Base. Il programma unificato è ottenuto dall unione delle componenti da preservare di Base con le differenze tra Base e A e con le differenze tra Base e B. Questo programma viene poi controllato per verificare che non vi siano interferenze. Software Maintenance: gli addetti al mantenimento di un programma dopo il suo rilascio si trovano di fronte a problemi simili a quelli dei program integrator: devono capire a fondo il software esistente ed integrare le eventuali modifiche senza che queste abbiano un impatto negativo sulle parti di codice non modificate. Per questo compito è possibile utilizzare un tipo particolare di slice, chiamata decomposition slice. Una decomposition slice è in grado di catturare tutte le istruzioni che hanno a che fare con una variabile, ed è indipendente dalla posizione nel programma. Una decomposition slice può risultare particolarmente utile quando i programmatori sanno che una variabile v dovrà cambiare valore nel corso dell esecuzione del programma. Testing: gli addetti al mantenimento di un programma spesso si trovano a dover effettuare dei regression test: cioè a ritestare il software dopo averlo modificato. Questo processo porta ad eseguire il programma in un gran numero di casi di test, anche per la più piccola delle modifiche. In letteratura sono stati presentati alcuni algoritmi che usano il program slicing per determinare quali componenti di un programma siano stati influenzati transitivamente da una modifica ad una istruzione p. Possono essere utilizzate slice multiple (backward e forward) oppure algoritmi che deducono i componenti da ritestare a partire da un grafo delle dipendenze. In alternativa è possibile utilizzare il program differencing discusso in precedenza per ridurre la dimensione del programma da ritestare. Debugging: effettuare il debug dei programmi è sempre stato 66

109 IS2 -> Capitolo 3 considerato un compito difficile. Spesso la parte più difficile del debugging sta proprio nello scoprire il bug. Questo è dovuto al fatto che nella porzione di codice da esaminare possono esserci molte istruzioni e non tutte hanno necessariamente effetto sull istruzione che effettivamente crea il problema. Uno strumento che calcola le program slice è un aiuto fondamentale per chi deve effettuare il debugging di un programma. Permette al programmatore di concentrarsi solo sulle istruzioni che contribuiscono ad un malfunzionamento, rendendo più efficiente l identificazione del punto di fault. Il dynamic slicing è in particolar modo utile per effettuare debugging legato ad uno specifico test case, poichè mette in luce tutte le istruzioni che certamente influiscono su una variabile. Software Quality Assurance: un particolare problema che gli auditor di software si trovano ad affrontare è la localizzazione del codice safety critical all interno dell applicazione e l identificazione degli effetti che queste porzioni di codice hanno su tutto il sistema. Il program slicing può essere utilizzato a questi fini per selezionare le istruzioni che influenzano il valore di una variabile che fa parte di una porzione safety critical del codice. In secondo luogo le tecniche di program slicing possono essere utilizzate per certificare la functional diversity (per esempio per assicurarsi che non ci siano interazioni tra due o più componenti safety critical e che non ci siano interazioni tra componenti non safety critical e componenti che invece lo siano). Il program slicing viene utilizzato per la validazione della safety in questo modo: per prima cosa gli auditor identificano i componenti critici del sistema utilizzando una fault tree analysis; le porzioni di software che vengono invocate durante una condizione pericolosa vengono quindi identificate. A questo punto gli auditor possono localizzare le variabili del programma che sono indicatrici di una condizione di pericolo. Le program slice sono quindi estratte da queste variabili interessanti e usate per verificare che non ci siano interazioni tra componenti critici e componenti non critici utilizzando le tecniche di dicing. Reverse Engineering: il reverse engineering ha a che fare con il problema della comprensione del design attuale di un programma e con il modo in cui questo programma risulta differente dal design originale. Il program slicing risulta utile per concentrare gli sforzi sulle porzioni di programma che hanno subito più modifiche durante la fase di manutenzione. Per identificarle sarà sufficiente creare una rete di slice ordinate secondo la relazione is-a-slice-of. Confrontando le due reti, quella tratta dal design originale e quella ricavata dal programma dopo anni di manutenzione, saranno subito evidenti le differenze e si potranno concentrare su quelle gli sforzi di reverse engineering. Sempre per aiutare questo processo è stata introdotta una speciale nozione di slicing: l interface slicing. Spesso infatti per poter procedere con il reverse engineering è necessario identificare le astrazioni principali dei moduli di un programma e le interfacce tra di esse. Una interface slice è essenzialmente una forward slice presa rispetto ai nodi di ingresso in una collezione di procedure. Il suo scopo è quindi quello di isolare i comportamenti che un certo modulo esporta al software che lo contiene. Slicing statico Dopo aver illustrato le enormi potenzialità che questa tecnica potrebbe avere nel ciclo di sviluppo del software, ritorniamo sino alla sua prima apparizione in letteratura: nel 1979, Mark Weiser pubblica un articolo in cui illustra la tecnica secondo un approccio di tipo statico. Weiser definisce il processo di computazione della slice usando solo informazioni statiche, senza fare quindi nessuna assunzione sull input; la program slice S è un programma eseguibile e ridotto ottenuto da un altro programma P, rimuovendo parti di codice, in maniera da mantenere comunque una consistenza funzionale rispetto allo slicing criterion (n,v). Lo slicing criterion, per un particolare programma, definisce una sorta di finestra di osservazione del comportamento del codice stesso. Questa finestra è definita tramite il numero di riga (n) e il set di variabili da controllare (V). Il programma ridotto possiede quindi idealmente due caratteristiche fondamentali: è minimale ed eseguibile. Minimale poichè contiene il minor numero di linee di codice tali da rappresentare tutti i punti di interesse per lo slicing criterion: non deve esistere un altra slice, per lo stesso criterio (n,v), 67

110 IS2 -> Capitolo 3 con un minor numero di istruzioni. Questa proprietà è importante poichè influisce sulla reale utilità del metodo: una slice, relativamente breve, focalizza l attenzione dello sviluppatore e del tester sulle parti fondamentali dell algoritmo, rimuovendo quelle parti non essenziali che generano confusione e ridondanza. Purtroppo però è possibile formalmente mostrare che nessun algoritmo può sempre calcolare una slice con la minor cardinalità, in quanto non è possibile valutare l equivalenza di due differenti parti di codice. Questa limitazione suggerisce di adattare il concetto di dimensione della slice specificatamente ad ogni singola analisi che si effettua. E necessario evidenziare come il limite massimo sia comunque definito dal programma completo che può essere considerato una slice esso stesso. La proprietà di eseguibilità è invece fondamentale per poter assicurare il corretto comportamento della slice a seguito dell operazione di cancellazione di righe di codice dal programma originale. Il comportamento desiderabile della slice deve essere uguale a quello del programma originale per le parti considerate e per tutti i possibili valori di input. Definire una slice sul codice significa solamente guardare lo stesso sistema (e quindi lo stesso comportamento) secondo una particolare finestra di osservazione. Questo requisito, che appare ragionevole, è però troppo difficile da soddisfare poichè il programma potrebbe anche non terminare. Godel e Turing dimostrando l indecidibilità della terminazione di un programma, portano Weiser a definire un vincolo meno stringente: il programma originale e la slice devono avere il medesimo comportamento se il programma originale termina. Con una slice dotata delle proprietà sopra illustrate è quindi possibile eseguire la slice e verificare il valore delle variabili considerate esattamente come se quel pezzo di codice fosse inserito all interno di un esecuzione che interessa tutto il programma. L approccio di Weiser viene supportato da un algoritmo che fa uso di una rappresentazione grafica intermedia del codice sorgente: il Data Flow Graph (DFG) su cui calcolare il set di istruzioni direttamente o indirettamente rilevanti, in accordo con le dipendenze di tipo data flow o control flow. Prima di analizzare questo algoritmo facciamo qualche breve considerazione sulle altre soluzioni implementative che, negli anni, sono state sviluppate. Oltre alle tecniche basate su Information-Flow Relation sono estremamente interessanti quelle basate sul Program Dependence Graph (PDG). Dopo aver costruito il grafo delle dipendenze del programma, il problema del calcolo della slice è ridotto ad un semplice problema di raggiungibilità a partire dal nodo rappresentante il punto di interesse nel grafo del programma. Da questo grafo, la computazione della slice risulta essere un processo efficiente poichè è possibile riusare lo stesso grafo per qualsiasi slicing criterion sullo specifico programma; una volta ottenuta la slice è necessario trasformarla nuovamente in istruzioni. L efficienza dell implementazione deriva dal fatto che sebbene il processo di costruzione del PDG è Θ(n 2 ) sul numero di istruzioni, il calcolo della slice, che può essere ripetuto per diversi criteri, è lineare rispetto alle dimensioni del grafo (e quindi rispetto al numero di linee di codice). In tutti gli approcci che abbiamo elencato, da un algoritmo base che considerava semplici programmi lineari si è poi passati ad introdurre tutti i costrutti particolari per specifici linguaggi. Algoritmo semplificato per straight line program Per aiutare il lettore nella comprensione dell algoritmo di slicing, secondo l approccio classico di Weiser, definiremo anche noi inizialmente l algoritmo per straight line program introducendo solo successivamente le modifiche al fine di adattarlo alla computazione della slice sull esempio proposto nell introduzione. Il Control Flow Graph (CFG) di un programma P è un grafo orientato in cui ad ogni nodo è associata un istruzione di P mentre ogni arco rappresenta il flusso di controllo in P stesso. Ogni nodo n nel grafo ha associati tre particolari insiemi: REF(n), come set di variabili referenziate in una particolare istruzione, 68

111 IS2 -> Capitolo 3 DEF(n), come set di variabili definite in n e REL(n), come set di variabili rilevanti per la particolare linea di codice. Con riferimento all assegnamento in figura2 possiamo vedere come vengono definiti gli insiemi REF e DEF. L algoritmo viene eseguito in modalità backward poichè il calcolo della slice avviene a partire dal punto su cui è definito il criterio, analizzando il grafo all indietro. A partire dal Control Flow Graph e dagli insiemi appena definiti, calcoliamo gli insiemi REL e la slice tramite il seguente algoritmo iterativo rispetto al criterio (n,v): Nell algoritmo così definito si ipotizza che ogni istruzione abbia un singolo predecessore, ovvero che il programma sia strettamente lineare. Per contemplare la presenza di modificatori di flusso (istruzioni if, while, etc.) dobbiamo introdurre alcune modifiche: considerare un nuovo insieme chiamato control set, eventuali iterazioni dovute a cicli nel grafo e definire una regola per unire gli insiemi REL; quest ultima operazione è necessaria nei punti di congiunzione del grafo (joinpoint) che corrispondono alle istruzioni che determinano la creazione di due o più branch nel codice. Il control set è un insieme che viene associato ad ogni nodo del grafo e contiene le etichette di tutti i nodi che sono direttamente rilevanti per l esecuzione di quel punto del codice; nel caso di un istruzione if else, i nodi contenuti nel ramo dell if-selection conterranno nel control set l indicazione dell istruzione if (il numero del nodo). Nei join points, ovvero nei punti in cui risalendo il grafo due nodi hanno lo stesso predecessore, il REL set sarà definito come quell insieme dato dall unione dei REL set dei due nodi successori considerando inoltre le variabili all interno del predicato di controllo che risultano indirettamente rilevanti per il calcolo della slice. Questa approssimazione, che certamente aumenta la dimensione della slice, è necessaria in quanto a priori, durante l analisi statica, non è possibile privilegiare un cammino rispetto ad un altro. Per gestire l eventuale presenza di loop nel grafo, dobbiamo anche contemplare la possibilità di iterare ulteriormente il nostro algoritmo al fine di portare a convergenza i REL set associati a nodi presenti nel loop. Hausler ha comunque mostrato come il numero massimo di iterazioni è pari al numero di assegnamenti presenti nel loop: risultando finito potrà essere computato in maniera automatica. 69

112 IS2 -> Capitolo 3 Dopo aver brevemente illustrato il funzionamento dell algoritmo di slicing secondo l approccio classico, vogliamo ora riportare un esempio di esecuzione di tale procedimento automatico sul codice che abbiamo presentato nell introduzione. Dall analisi del codice è possibile costruire il Control Flow Graph di figura 3 e su questo applicare il procedimento illustrato con il criterio (10, product), a partire appunto dal nodo 10. Nelle tabelle 1 e 2, i nodi contrassegnati con il carattere sono quelli che soddisfano la condizione indicata all interno del predicato if nel punto 3 dell algoritmo e che quindi compongono la slice. E da notare come solamente alla seconda iterazione (vedi tabella 2), considerando le variabili indirettamente rilevanti presenti nel predicato di controllo dell istruzione while, la slice costruita è uguale a quella che ci si aspettava analizzando manualmente il codice, come visto nell introduzione. Altro esempio di CFG Dynamic slicing Una slice statica preserva il comportamento di un programma rispetto al criterio di slicing e rispetto ad ogni possibile input (per questo statica). Una slice dinamica preserva il comportamento del programma rispetto ad un particolare input e quindi rispetto ad un unica traiettoria di stato. Permette di calcolare slice più piccole molto utile per il debugging. La nozione classica di program slice non tiene conto dei valori di input del programma; spesso però risulta particolarmente utile nel debugging tenere traccia del comportamento del programma nelle condizioni che hanno determinato un bug. Korel e Laski affrontano il problema proponendo la controparte dinamica del program slicing classico introdotto da Weiser. Lo slicing dinamico è in grado di trovare tutte le istruzioni che hanno realmente influito sul valore di una variabile per una particolare esecuzione di un programma. Per calcolare la slice dinamica Korel e Laski prendono spunto dall algoritmo di Weiser per lo slicing statico introducendo una soluzione basata su equazioni data-flow. Le slice dinamiche sono calcolate a partire da 70

113 IS2 -> Capitolo 3 una execution history (traiettorie di stato). La execution history è una lista delle istruzioni del programma registrate durante la sua esecuzione nell ordine in cui esse sono state effettivamente eseguite. La execution history di un programma in un certo caso di test viene indicata con < x1, x2,..., xn > in cui xi indica la i-esima istruzione del programma. Nel caso un istruzione venga eseguita più volte, per esempio se si trova all interno di cicli, le diverse istanze vengono differenziate da un numero in apice. In letteratura sono state presentate numerose soluzioni ai problemi più complessi legati allo slicing dinamico, ad esempio per la gestione di puntatori, array e, ovviamente, per supportare a pieno i linguaggi ad oggetti. Tuttavia ci concentreremo solo sulle soluzioni base, poiché le ottimizzazioni proposte riprendono la filosofia e gran parte del modus operandi di questi algoritmi. Data Flow Problem Korel e Laski definiscono una slice dinamica come un programma eseguibile ridotto. Le tre equazioni data flow introdotte da Korel e Laski formalizzano le dipendenze tra le occorrenze di un istruzione nella execution history. La relazione Definition-Use (DU) associa l uso di una variabile con la sua ultima definizione. La relazione Test-Control (TC) associa la più recente occorrenza di un predicato di controllo con le occorrenze delle istruzioni che sono dipendenti da quel predicato. Le occorrenze della stessa istruzione sono legate dalla Identity Relation (IR). Program Dependence Graph (PDG) Utilizzato nell approccio proposto da Ottenstein, il Program Dependence Graph, abbreviato PDG, è un grafo che ha un nodo per ogni istruzione semplice (assegnamenti, letture, scritture, etc.) e un nodo per ogni espressione di un predicato di controllo (l espressione condizionale di un costrutto if-then-else, while, etc.). Il Program Dependence Graph ha due tipi di archi orientati: archi data-dependence e archi controldependence. Un arco data-dependence dal nodo vi al nodo vj implica il fatto che l elaborazione effettuata al nodo vi dipenda direttamente dal valore calcolato al vertice vj. Un arco control-dependence dal nodo vi al nodo vj indica che il nodo vi potrebbe o meno essere eseguito a seconda del risultato booleano dell espressione del predicato di controllo al nodo vj. 71

114 IS2 -> Capitolo 3 La figura 7 mostra il Program Dependence Graph del programma in figura 6 in cui gli archi datadependence sono tracciati con linee continue e gli archi controldependence con linee tratteggiate. Poichè per il calcolo della slice dinamica non ci interessa distinguere tra i due tipi di archi, d ora in avanti entrambi verranno indicati con linee continue. Traiettoria di stato Una traiettoria di stato di un programma P è una sequenza di coppie T=(p1,s1),(p2,s2),...,(pi,si) dove per ogni i: P è un nodo del CFG di P p1, p2,..., pi è un cammino. un criterio di slice di un programma P è una coppia C=(p,V) dove p è un istruzione (nodo del CFG) di P e V è un sottoinsieme delle variabili di P. Un criterio di slicing determina una funzione di proiezione Proj c che elimina da un traiettoria di stato tutte le copie che non iniziano con p e restringe la funzione di mapping s alle sole variabili in V. Calcolo della slice minima Problemi di indecidibilità: non è possibile calcolare la slice minima dati un programma P e un criterio di slicing C =(p, V) (derivata dall indecidibilità del problema della terminazione). Approssimazione: in pratica a partire dall istruzione p, calcolare tutte le istruzioni che influenzano direttamente o indirettamente i valori che assumono le variabili in V immediatamente prima dell esecuzione di p. Vi sono diversi algoritmi proposti, ma il più affascinante resta quello basato sul program dependence graph. Il concetto di influenza di Weiser può infatti essere espresso in termini di due tipi di dipendenza: dipendenza sul controllo dipendenza sui dati Il calcolo di una slice si riduce ad un problema di raggiungibilità all interno (backward) sugli archi di un program dependance graph. In questo caso il criterio di slicing è del tipo (p, v) dove p è il nodo e v è una variabile referenziata in p. Grafo sulle dipendenze di controllo Dati un CFG e due nodi n ed m in N M postdomina n se ogni cammino da n ad n, contiene m M è dipendente sul controllo da n se: o Esiste un cammino (p1,p2,..,pk) con n = p, e m=pk, tale che m postdomina o M non postdomina n Informalmente questo significa che il nodo n rappresenta un predicato ossia ha due archi uscenti: 72

115 IS2 -> Capitolo 3 seguendo un arco m viene sicuramente eseguito seguendo l altro arco m non viene necessariamente eseguito. Dipendenze sui dati Esistono diversi tipi di dipendenze sui dati definite in letteratura: in particolare la dipendenza sul flusso dati è quella che è utilizzata per il calcolo di una slice. Dati un control flow graph CFG = (N, E, ni, nf) di un programma P, due nodi n ed m in N e una variabile v di P, DEF(n) è l insieme delle variabili definite al nodo n; USE(m) è l insieme delle variabili usate al nodo m; DEF_USE(P) è l insieme delle triple definizione-uso (n, m, v), dove v è definita in n ed usata in m ed esiste un cammino da n ad m (esclusi n ed m) su cui v non è ridefinita (def-clear path). Backward vs forward slicing Parlando di static e dynamic slicing abbiamo già introdotto il concetto di analisi backward. Lo slicing può però essere implementato anche in modalità forward. Questo nuovo modo di procedere è utile quando dobbiamo capire quali parti, del codice sorgente, vengono influenzate da una determinata modifica; è quindi una tecnica utile nel caso di integrazione di codice piuttosto che per il debugging. Se l analisi backward serve per rispondere alla domanda: Quali istruzioni influenzano questo punto?, l analisi di tipo forward risponde al quesito: Quali istruzioni risentiranno di una modifica effettuata in un determinato punto del codice?. A livello implementativo, utilizzando il dependence graph, si tratta solamente di effettuare la copertura nella direzione opposta cui operano gli algoritmi precedentemente visti. Spesso a procedimenti di tipo backward vengono affiancate iterazioni forward, dando origine a tecniche di slicing miste denominate complete slicing. In letteratura sono poi stati definiti approcci misti di tipo statico e dinamico, chiamati quasi-static, in cui solamente parte degli input, per la creazione dell execution history, è noto. Sempre considerando gli approcci misti, è da citare il dicing definito come una tecnica che effettua operazioni insiemistiche su diverse slice. L applicazione classica è quella in cui si effettua la differenza tra due slice, calcolate su due slicing criterion differenti, al fine di evidenziare delle particolarità del software. Consideriamo, per esempio, un software difettoso che effettua il conteggio di caratteri e parole all interno di un testo in cui il valore dei caratteri risulta corretto mentre quello delle parole errato. Su questo semplice programma possiamo ricavare due diverse backward slice a partire dall istruzione di stampa del numero di caratteri e di parole ed applicare dicing per capire tutte le istruzioni che determinano il conteggio delle 73

116 IS2 -> Capitolo 3 parole ma non quello dei caratteri; tra di esse ci saranno le istruzioni che determinano il malfunzionamento dell applicazione. Per completezza riportiamo anche un approccio differente, chiamato chopping, che risulta utile quando si desidera capire l influenza di una modifica nell applicazione generale. Queste operazioni sono comuni durante lo sviluppo di software in ambiente collaborativo poichè solitamente si effettua program integration ad intervalli molto brevi. Definendo un criterio del tipo chop(t,s), la slice conterrà tutti i punti del programma che trasmettono gli effetti di una modifica in un punto t rispetto ad un punto del codice s. Questo procedimento è realizzato tramite l intersezione di una forward slice presa rispetto a t e una backward slice rispetto a s. Slicing interprocedurale Considera le chiamate tra procedure. Una slice contiene statement di diverse procedure che influenzano i valori assunti dalle variabili del criterio di slicing (p, V) prima dell esecuzione di p. Un primo algoritmo proposto da Weiser produce slice imprecise (sovradimensionate). Slicing inter-procedurale e system dependence graph (SDG) Horwitz et al, hanno proposto un algoritmo più preciso basato sul system dependence graph, l estensione a livello interprocedurale del program dependence graph. Fase 1: non entra nelle funzioni chiamate, usa dipendenze tra i parametri reali Fase 2: non risale alla funzione chaimante È stato usato per l estrazione di funzioni. Una slice S su un criterio di slicing di un programma rappresenta un insieme di slice che costituiscono una decomposizione del programma. Ci sono diversi criteri che si possono adottare per determinare la funzione da testare. Un approccio è quello di fermare il calcolo della slice appena si incontra la definizione di variabili di input per la funzione cercata. Conditioned slicing È una generalizzazione delle tecniche di slicing basate sulla eliminazione di statement. L insieme dei possibili input al programma è definito da una formula logica sulle variabili di ingresso. Conclusioni Fare slicing in maniera automatica è sicuramente un buon approccio per cercare di ridurre la crescente complessità dei sistemi. La letteratura, in questi vent anni dalla prima pubblicazione, è fiorente di attività di ricerca e di soluzioni teoriche per cercare di calcolare la slice in maniera efficiente e con buoni risultati. A fronte di queste basi teoriche non risultano però altrettanto interessanti gli strumenti a disposizione dello sviluppatore che abbiamo potuto provare; essi risultano spesso incompleti ed in versione prototipale, non disponibili per tutti i linguaggi di programmazione e per tutti i costrutti di tali linguaggi. Alcuni problemi e limitazioni, come quelli intrinseci nel metodo statico, sono superati teoricamente con alcune 74

117 IS2 -> Capitolo 3 approssimazioni durante l esecuzione dell algoritmo di slicing ma, a livello pratico, conducono alla costruzione di slice con un alto numero di righe. VERIFICA STATICA, ISPEZIONE E ANALISI STATICA IN COMPILAZIONE Ispezione Le ispezioni del software sono un processo V&V statico in cui un sistema software viene revisionato per cercare errori, omissioni e anomalie. In genere le ispezioni si concentrano sul codice sorgente. Quando si ispeziona un sistema, per scoprire gli errori si utilizza la conoscenza del sistema, del suo dominio di applicazione e del linguaggio di programmazione. Sono tre i vantaggi dell ispezione rispetto al test: 1. Durante il test gli errori possono mascherare altri errori, una volta scoperto un errore non si può essere sicuri che altre anomalie dell output siano dovute all effetto dell errore originario. Una singola sessione di ispezione può scoprire molti errori in un sistema. 2. Le versioni incomplete di un sistema per essere ispezionate si deve sviluppare una struttura di test specializzata per testare le parti disponibili, e questo comporta costi di sviluppo maggiori per il sistema. 3. Oltre a cercare difetti, un ispezione può esaminare anche attributi di qualità come, la conformità agli standard, la portabilità e la manutenibilità. Anomalie del codice possono influenzare in modo scorretto il codice: esempi: distribuzione linee bianche, numero linee di commento. Queste anomalie posso essere evitate tramite tecniche di ispezione di codice che possono rilevare ed eliminare anomalie fastidiose e rendere più precisi i risultati. Risulta essere una tecnica completamente manuale per trovare e correggere errori: poco tecnologica, ma efficace ma sono possibili alcuni supporti automatici. ed inoltre è estendibile a progetto, requisiti, seguendo principi organizzativi analoghi. Con l ispezione, inizialmente è stato pensato come dovrebbe essere fatto il codice e se rispetta determinate regole. Ruoli nell ispezione di software moderatore capo: tipicamente proviene da un altro progetto (organizza il processo di ispezione). Presiede le sedute, sceglie i partecipanti, controlla il processo Ispettore: trova errori, omissioni e inconsistenze nei programmi e nei documenti; può anche identificare problemi più vasti che esulano dal campo d azione del team. lettori, addetti al test: leggono il codice al gruppo, cercano difetti e successivamente si fa un meeting discutendo i punti di contrasto. Si ripete la lettura del codice, si cerca di simulare il codice. Al meeting partecipa anche l autore che cerca di chiarire alcuni punti non chiari. autore: partecipante passivo; risponde a domande quando richiesto. Il programmatore o il progettista responsabile di produrre il programma o il documento. È responsabile anche della correzione dei difetti scoperti durante il processo di ispezione. Il processo di ispezione del software La differenza chiave tra l ispezione del programma e altri tipi di revisione qualitativa è l obiettivo specifico delle ispezioni: trovare i difetti del programma anziché esaminare problemi di progettazione più ampi. I difetti possono essere errori logici, anomalie nel codice ecc. L ispezione del programma è un processo formale seguito da una squadra di almeno 4 persone che analizza sistematicamente il codice e individua i possibili difetti. La proposta originale di Fagan suggeriva ruoli come autore, lettore, tester e moderatore. Il 75

118 IS2 -> Capitolo 3 lettore legge ad alta voce il codice al team, il tester ispeziona il codice da una prospettiva di test e il moderatore organizza il processo. Con l acquisizione di esperienza da parte delle organizzazioni sono emerse proposte di altri ruoli per i membri della squadra, sei ruoli (vedi paragrafo appena sopra). Pianificazione: scelta di partecipanti e checklist, pianificazione di meeting fasi preliminari fornite le informazioni necessarie e assegnare i ruoli preparazione lettura del documento, individuazione dei difetti Ispezione meeting: raccolta e discussione congiunta dei problemi trovati dai singoli revisori, ricerca di ulteriori difetti lavoro a valle l autore modifica il documento per rimuovere i difetti seguito (possibile re-ispezione) controllo delle modifiche, raccolta di dati Nelle riunioni si ha l obiettivo di trovare il maggior numero possibile di difetti, bisogna non stancarsi troppo ed essere sempre lucidi: massimo 2 riunioni al giorno di 2 ore ciascuna, circa 150 linee di codice sorgente all ora. L approccio è quello di parafrasare il codice linea per linea, ricostruire l obiettivo dal codice sorgente tramite ad esempio test manuale. È necessario restare in tema e seguire le checklist, bisogna trovare e registrare difetti ma non correggerli e il moderatore è responsabile di evitare anarchia. Durante la fase di ispezione si può incombere in errori che sono elencati nella tabella: 76

119 IS2 -> Capitolo 3 Checklist Esempio NASA Circa 2.5 pagine per codice C, 4 per FORTRAN. Viene diviso in: funzionalità, uso dei dati, controllo, connessioni, calcolo, manutenzione, chiarezza. Esempi: ogni modulo contiene una singola funzione? il codice corrisponde al progetto dettagliato? i nomi di costante sono maiuscoli? si usa il cast di tipo dei puntatori? INCLUDE annidati di files sono evitati? gli usi non standard sono isolati in sottoprogrammi e ben documentati? i commenti sono sufficienti per capire il codice? Incentivi Se i difetti trovati nell ispezione non sono usati nella valutazione personale, i programmatori non hanno motivo di nascondere i difetti. I difetti trovati durante il test (dopo l ispezione) sono usati nella valutazione personale e i programmatori sono incentivati a trovare difetti nell ispezione, ma non inserendone intenzionalmente. L ispezione viene fatto prima che il codice viene testato, ossia si cerca di trovare i maggiori fallimenti di codice per ridurre il costo di manutenzione. Si cerca di aumentare la qualità del codice e diminuire i costi di test. Varianti Revisione attiva di progetto (Parnas & Weiss, 1985): un revisore impreparato può sedere in silenzio ed essere inutile, a tal motivo bisogna scegliere revisori con esperienze specifiche (revisori differenti devono cercare difetti differenti). Ispezione a fasi (Knight & Meyers, 1993): una serie di fasi più piccole che mettono a fuoco problemi specifici in un ordine determinato. Un ispettore singolo (e meno esperto) per controlli semplici mentre più ispettori (esperti) per controlli complessi. Perché l ispezione funziona? L evidenza dice che è cost-effective, perchè? Processo formale, dettagliato con tracciamento dei risultati Check-lists: processo che si automigliora Aspetti sociali del processo, specialmente per gli autori, attività fatta in team e di collaborazione Considera l intero spazio di ingresso 77

120 IS2 -> Capitolo 3 Si applica anche a programmi incompleti, non devo avere necessariamente un programma completo ed in esecuzione, posso anche fare il test su una singola funzione, senza che necessariamente vi siano gli stub per eseguire la classe. Esempio: Checklist per codice Java I Commenti vanno controllati nella sintassi e semantica Sintassi: controlli ortografici e grammaticali della lingua Struttura, come è organizzato Stile Contenuto Anche per il codice vi è Stile Correttezza Semantica Ridondanza Qualità Commenti: Sintassi e Struttura Sintassi: controlli ortografici, grammaticali e linguistici, commenti formattati per javadoc Struttura: ci sono dati identificativi (titolo, data, versione, autore), librerie non standard usate, piattaforma Java richiesta. Commenti: Stile Non c è un uso indiscriminato di abbreviazioni Non ci sono troppe ripetizioni Linguaggio comprensibile e frasi leggibili Il commento è presente in ogni classe Il commento è ben visibile nel file Le variabili sono commentate quando sono dichiarate I commenti non sono dispersivi (mal disposti, interrotti e ripresi) I commenti non sono esageratamente densi nel codice (compromettendo la leggibilità del codice) Commenti: Contenuto L intestazione dei metodi e delle classi corrispondono ai commenti Il commento descrive i metodi principali della classe: Commentati tutti i metodi con il corpo più lungo di tre LOC Sono spiegati i passaggi più difficili: I commenti non sono mai banali Il commento dà indicazione chiara (almeno generale) della funzione della classe Il commento è completo in ogni sua parte Il commento è contestuale (pertinente al codice circostante), sta vicino al codice Codice: Stile Gli identificatori di costante sono espressi con una convenzione diversa dagli identificatori di variabile I nomi delle classe sono espressi con una convenzione diversa dagli identificatori di variabile I nomi delle variabili e delle classi sono significativi Il codice si presta alla lettura oppure è troppo concentrato o troppo diluito: Si applica uno stile, Lo stile è leggibile, Lo stile è mantenuto per tutto il progetto, Lo stile prevede una buona indentazione Codice: Correttezza semantica e Ridondanza Correttezza semantica L esecuzione del codice rispetta le specifiche dei commenti 78

121 IS2 -> Capitolo 3 Non vengono usate librerie non standard Ridondanza Non ci sono variabili dichiarate ma non utilizzate Non ci sono metodi con funzionalità simili Non ci sono variabili con funzionalità simili Codice: Qualità Ogni classe ha almeno un costruttore, Altrimenti Ha solo metodi statici o Si tratta di una classe astratta o di un interfaccia Non si usano metodi con side-effects non necessari Non si usano variabili globali non necessarie C è omogeneità nella dimensione delle classi Ci sono metodi equilibrati o Non ci sono metodi che incorporano troppe funzionalità e altri troppo elementari Ogni metodo rispecchia una funzionalità precisa Non si fa uso di metodi deprecati Si fa uso di eccezioni o Fanno effettivamente riferimento a situazioni anomale o Vengono usate in modo coerente o Coprono tutte le parti critiche Si fa buon uso del paradigma ad oggetti o Buon uso di ereditarità, polimorfismo Si usa uno stile elegante nella stesura del codice o Non troppi casting, buon uso delle funzionalità di Java Analisi statica automatica L obiettivo dell analisi statica automatica è portare all attenzione le anomalie del programma, come le variabili che sono utilizzate senza inizializzazione, quelle inutilizzate o i dati il cui valore può uscire fuori dal campo di variazione. Le anomalie sono spesso errori o omissione da parte dei programmatori, quindi evidenziano cose che possono fallire quando il programma viene eseguito. Si deve però comprendere che queste anomalie non sono necessariamente errori del programma. 79

122 IS2 -> Capitolo 3 L analisi statica è suddivisa in diverse fasi: 1. Analisi del flusso di controllo: identifica cicli con punti di uscita e di ingresso e codice irraggiungibile. 2. Analisi dell uso dei dati: evidenzia come sono utilizzate le variabili nel programma, individua quelle utilizzate, quelle scritte più volte senza essere lette e quelle dichiarate senza mai essere utilizzate, inoltre scopre test inefficienti con condizioni di test ridondanti, ovvero quelle sempre vere o sempre false. 3. Analisi delle interfacce: controlla la consistenza delle dichiarazioni delle routine e delle procedure e del loro utilizzo. 4. Analisi del flusso di informazioni: identifica le dipendenze tra le variabili di input e output. Può anche mostrare le condizioni che influenzano il valore di una variabile. 5. Analisi del percorso: identifica tutti i percorsi attraverso il programma e le istruzioni eseguite. 80

123 IS2 -> Capitolo 4 CAPITOLO 4: SOFTWARE DESIGN OBJECT DESIGN & DESIGN PATTERN Lo scopo dell object design è quello di chiudere il divario tra oggetti di applicazione e componenti off-theshelf per mezzo dell identificazione di nuovi oggetti di soluzione e la raffinazione di oggetti già esistenti. Serve come base dell implementazione; durante la fase dell object design, infatti, si prendono decisioni su come implementare il modello d analisi. Inoltre si aggiungono dettagli all analisi dei requisiti. Include: -Riuso -Specifica dei servizi -Ristrutturazione del modello ad oggetti -Ottimizzazione del modello ad oggetti Riuso Le componenti off-the-shelf identificate durante il system design vengono utilizzate nella realizzazione di ogni sottosistema; vengono selezionati: librerie di classi e componenti utili per strutture dati e servizi basilari; Design Patterns per proteggere le classi da eventuali cambiamenti futuri e per risolvere vari problemi frequenti. Specifica dei servizi Tutti i servizi identificati durante il system design sono trasformati in interfacce di classi (argomenti, firme, operazioni ed eccezioni). Inoltre, si aggiungono alcune operazioni ed oggetti utili per far fronte ai trasferimenti dei dati tra i vari sottosistemi. Ogni sottosistema, alla fine di questa fase di specifica, avrà le proprie interfacce. Tale specifica è spesso chiamata API (Application Programmer Interface) Ristrutturazione del modello ad oggetti Al fine di aumentare il riuso e di soddisfare altri obiettivi importanti per lo sviluppo del sistema (design goals), il modello ad oggetti viene modificato (trasformazioni di associazioni n-arie in associazioni binarie, di associazioni binarie in riferimenti, fusione di classi di simile natura in una sola classe e decomposizione di classi complesse in varie classi più semplici, trasformazioni di classi che mancano di uno specifico comportamento in attributi, aumento dell ereditarietà e del packaging tramite modifica di classi ed operazioni). Inoltre, ci si occupa anche del mantenimento, della leggibilità e della comprensione di tale modello. Ottimizzazione del modello ad oggetti Al fine di migliorare il modello di sistema, si fa in modo che quest ultimo soddisfi anche i requisiti di performance. Avvengono quindi: modifiche agli algoritmi (per soddisfare requisiti di velocità e memoria), riduzione delle associazioni (per rendere le query più veloci), aggiunta di associazioni ridondanti (per aumentare l efficienza), aggiunta di attributi derivati (per ridurre il tempo di accesso agli oggetti), apertura dell architettura (concedere l accesso anche agli strati più bassi del sistema). RIUSO Durante l object design oggetti di applicazione (entity e relazioni tra gli entity), identificati nell analisi, e oggetti di soluzione (boundary e control), identificati nell analisi e nel system design, vengono raffinati e dettagliati e c è l identificazione di nuovi oggetti di soluzione, per far sì che il divario di object design si colmi. Oggetti di applicazione = oggetti che rappresentano concetti rilevanti per il sistema Oggetti di soluzione = oggetti che non rappresentano concetti, ma componenti che non hanno nessun riscontro nel dominio di applicazione (oggetti dell interfaccia utente, dati persistenti, ecc.). 81

124 IS2 -> Capitolo 4 Si riducono le ridondanze e si migliora l estensibilità tramite due tipi di ereditarietà: di specifica e di implementazione. Ereditarietà di specifica = ereditarietà utile per classificare i concetti in tipi di gerarchia Ereditarietà di implementazione = ereditarietà utile per dare la possibilità di riutilizzare il codice in futuro Delegazione = attività utile a favorire il riuso (può essere anche un alternativa all ereditarietà), che consiste nel far sì che una classe implementi una operazione (un metodo) ri-inviando un messaggio ad un altra classe. La delegazione, quindi, comporta una stretta dipendenza tra classi delegate e classi deleganti. Principio di sostituzione di Liskov = principio che afferma che se un codice cliente usa i metodi forniti da una superclasse, allora per gli sviluppatori c è la possibilità di aggiungere nuove sottoclassi senza modificare il codice cliente. Delegazione ed Ereditarietà = dato in molte situazioni è difficile capire se l ereditarietà sia più adatta della delegazione o viceversa, i design patterns rappresentano un grande aiuto per l implementazione. DESIGN PATTERN Design Pattern = template di soluzioni, che sono stati migliorati con il tempo e che sono descritti ognuno da quattro elementi: nome, che lo identifica descrizione del problema, che descrive le situazioni in cui il pattern può essere utile soluzione, che è un insieme di classi ed interfacce conseguenze, che rappresentano i trade-off, quindi gli svantaggi e i vantaggi per ogni pattern di fronte ai vari requisiti e agli obiettivi fissati. Vi sono tre categorie: Creazionali (forniscono meccanismi per creare oggetti), Strutturali (separano interfaccia ed implementazione e gestiscono le modalità di composizione degli oggetti), Comportamentali (permettono di modificare il comportamento degli oggetti minimizzando le necessità di cambiare il codice). Composite Nome : Composite design pattern Descrizione del problema : Il design pattern Composite organizza gli oggetti in una struttura ad albero, nella quale i nodi sono delle composite e le foglie sono oggetti semplici. È utilizzato per dare la possibilità al cliente di manipolare gli oggetti in modo uniforme. Struttura del pattern Composite Client: manipola gli oggetti attraverso l'interfaccia Component. Component: dichiara l'interfaccia per gli oggetti, per l'accesso e la manipolazione di questi, imposta un comportamento di default per l'interfaccia comune a tutte le classi e può definire un'interfaccia per l'accesso al padre del componente e la implementa se è appropriata. Composite: definisce il comportamento per i componenti aventi figli, salva i figli e implementa le operazioni ad essi connesse nell'interfaccia Component. Leaf: definisce il comportamento degli oggetti primitivi, cioè delle foglie. 82

125 IS2 -> Capitolo 4 Attraverso l'interfaccia Component, il Client interagisce con gli oggetti della composite. Se l'oggetto desiderato è una Leaf, la richiesta è processata direttamente; altrimenti, se è una Composite, viene rimandata ai figli cercando di svolgere le operazioni prima e dopo del rimando. In questo modo, si semplifica il Client, si creano delle gerarchie di classi, si semplifica l'aggiunta di nuovi componenti, anche se il design diventa troppo generale. Usiamo il Composite pattern per descrivere la decomposizione dei sottosistemi. Un sottosistema è composto da classi e altri sottosistemi. Concetti correlati: Facade Pattern Façade Letteralmente façade significa "facciata", ed infatti nella programmazione ad oggetti indica un oggetto che permette, attraverso un'interfaccia più semplice, l'accesso a sottosistemi che espongono interfacce complesse e molto diverse tra loro, nonché a blocchi di codice complessi. Consideriamo, ad esempio, la seguente situazione in cui una classe Client, per realizzare una singola operazione deve accedere ad alcune classi molto differenti tra loro. L'utilizzo del pattern façade (qui realizzato attraverso la classe Facade) permette di nascondere la complessità dell'operazione, poiché in questo caso la classe Client chiama soltanto il metodo metodounico per realizzare la stessa operazione. Il vantaggio è ancora più evidente se questo pattern viene utilizzato in una libreria software, poiché rende indipendente l'implementazione della classe Client dall'implementazione dei vari oggetti Class1, Class2, etc. Bridge Pattern Il bridge pattern è un design pattern (modello di progettazione) della programmazione ad oggetti e permette di separare l'interfaccia di una classe (che cosa si può fare con la classe) dalla sua implementazione (come si fa). In tal modo si può usare l'ereditarietà per fare evolvere l'interfaccia o l'implementazione in modo separato. 83

126 IS2 -> Capitolo 4 Conseguenze: il client è protetto dall implementazione astratta e concreta. Interfacce e implementazioni potrebbero essere testate separatamente Soluzione: Soluzione: L Abstraction class definisce l interfaccia visibile al client. L Implementor è una classe astratta che definisce l accessibilità dei metodi a basso livello dell Abstraction. Un istanza dell Abstraction mantiene una referenza alla corrispondente istanza di Implementr. Abstraction e Implementor possono essere raffinati indipendentemente. Conseguenze: Il Client è protetto dal implementazione astratta e concreta. Le interfacce e le implementazioni possono essere ridefinite/raffinate indipendentemente. Il Bridge Pattern interface è realizzato da un Abstraction class, il suo comportamento è di selezionare un ConcreteImplementor class. Il design patter puo essere esteso fornendo un nuovo RafindeAbstraction o un ConcreteImplementor class. Questo pattern è un classico esempio della combinazione delle specifiche e delle delegazioni per ereditare entri sia riuso che funzionalità. Adapter Pattern Con il nome adapter, o adattatore si denota un design pattern utilizzato in informatica nella programmazione orientata agli oggetti. A volte viene chiamato wrapper (ovvero involucro) per il suo schema di funzionamento. Il fine dell'adapter è di fornire una soluzione astratta al problema dell'interoperabilità tra interfacce differenti. Il problema si presenta ogni qual volta nel progetto di un software si debbano utilizzare sistemi di supporto (come per esempio librerie) dotati di interfaccia non perfettamente compatibile con quelle richieste da applicazioni già esistenti. Invece di dover riscrivere parte del sistema, oneroso e non sempre possibile se non si ha a disposizione il codice sorgente, può essere comodo scrivere un Adapter che faccia da tramite tra le diverse interfacce, rendendole così compatibili. L'Adapter è un pattern strutturale che può essere basato sia su classi che su oggetti. Questo design pattern è parte fondamentale della programmazione a oggetti ed è stato formalizzato per la prima volta da Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides - la cosiddetta gang of four - nel libro Design Patterns. Soluzione : 84

127 IS2 -> Capitolo 4 Un Adapter class implementa ClientInterface attesa dal Client. L Adapter delega la richiesta del Client al LegacyClass e compie ogni conversione necessaria. Conseguenze: Client e LegacyClass lavorano insieme senza compiere modificare al Client o al LegacyClass. L Adapter lavora con LegacyClass e tutte le sue sottoclassi. Un nuovo Adapter necessita di essere scritto per ogni specializzazione, esempio sottoclassi, di ClientInterface. Il Bridge copre bene il divario nelle interfacce e nella loro implementazione. Proxy pattern Un proxy, nella sua forma più generale è una classe che funziona come interfaccia per qualcos'altro. L'altro potrebbe essere qualunque cosa: una connessione di rete, un grosso oggetto in memoria, un file e altre risorse che sono costose o impossibili da duplicare. Un esempio ben conosciuto di proxy pattern è l'oggetto reference dei puntatori. Nelle situazioni in cui molte copie di un oggetto complesso devono esistere, il proxy pattern può essere adottato per incorporare il Flyweight pattern per ridurre l'occupazione di memoria dell'oggetto. Tipicamente viene creata un'istanza di oggetto complesso, e molteplici oggetti proxy, ognuno dei quali contiene un riferimento al singolo oggetto complesso. Ogni operazione viene svolta sui proxy e viene trasmessa all'oggetto originale. Un volta che tutte le istanze del proxy sono distrutte, l'oggetto in memoria può essere deallocato. 85

11. Evoluzione del Software

11. Evoluzione del Software 11. Evoluzione del Software Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 11. Evoluzione del Software 1 / 21 Evoluzione del Software - generalità Cosa,

Dettagli

12. Evoluzione del Software

12. Evoluzione del Software 12. Evoluzione del Software Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 12. Evoluzione del Software 1 / 21 Evoluzione del Software - generalità Cosa,

Dettagli

Processi (di sviluppo del) software. Fase di Analisi dei Requisiti. Esempi di Feature e Requisiti. Progettazione ed implementazione

Processi (di sviluppo del) software. Fase di Analisi dei Requisiti. Esempi di Feature e Requisiti. Progettazione ed implementazione Processi (di sviluppo del) software Fase di Analisi dei Requisiti Un processo software descrive le attività (o task) necessarie allo sviluppo di un prodotto software e come queste attività sono collegate

Dettagli

Il software: natura e qualità

Il software: natura e qualità Sommario Il software: natura e qualità Leggere Cap. 2 Ghezzi et al. Natura e peculiarità del software Classificazione delle qualità del software Qualità del prodotto e del processo Qualità interne ed esterne

Dettagli

Ingegneria dei Requisiti

Ingegneria dei Requisiti Corso di Laurea Specialistica in Ingegneria Informatica Corso di Ingegneria del Software A. A. 2008 - Ingegneria dei Requisiti E. TINELLI Contenuti I requisiti del software Documento dei requisiti I processi

Dettagli

5. Requisiti del Software II

5. Requisiti del Software II 5. Requisiti del Software II Come scoprire cosa? Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 5. Requisiti del Software II 1 / 42 Sommario 1 Generalità

Dettagli

Modellazione di sistema

Modellazione di sistema Corso di Laurea Specialistica in Ingegneria Informatica Corso di Ingegneria del Software A. A. 2008 - Modellazione di sistema E. TINELLI Contenuti Approcci di analisi Linguaggi di specifica Modelli di

Dettagli

Processo parte VII. Strumenti. Maggiore integrazione. Sviluppo tecnologico

Processo parte VII. Strumenti. Maggiore integrazione. Sviluppo tecnologico Strumenti Processo parte VII Leggere Cap. 9 Ghezzi et al. Strumenti software che assistono gli ingegneri del software in tutte le fasi del progetto; in particolare progettazione codifica test Evoluzione

Dettagli

Ingegneria del Software Requisiti e Specifiche

Ingegneria del Software Requisiti e Specifiche Ingegneria del Software Requisiti e Specifiche Obiettivi. Affrontare i primi passi della produzione del software: la definizione dei requisiti ed il progetto architetturale che porta alla definizione delle

Dettagli

TECNICO SUPERIORE PER LO SVILUPPO DEL SOFTWARE

TECNICO SUPERIORE PER LO SVILUPPO DEL SOFTWARE ISTRUZIONE E FORMAZIONE TECNICA SUPERIORE SETTORE I.C.T. Information and Communication Technology TECNICO SUPERIORE PER LO SVILUPPO DEL SOFTWARE STANDARD MINIMI DELLE COMPETENZE TECNICO PROFESSIONALI DESCRIZIONE

Dettagli

5. Requisiti del Software II

5. Requisiti del Software II 5. Requisiti del Software II Come scoprire cosa? Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 5. Requisiti del Software II 1 / 22 Sommario 1 Generalità

Dettagli

Architettura SW Definizione e Notazioni

Architettura SW Definizione e Notazioni Corso di Laurea Specialistica in Ingegneria Informatica Corso di Ingegneria del Software A. A. 2008 - Stili Architetturali E. TINELLI Architettura SW Definizione e Notazioni Definizione ANSI/IEEE Std Std1471-2000

Dettagli

Il ciclo di vita del software

Il ciclo di vita del software Il ciclo di vita del software Il ciclo di vita del software Definisce un modello per il software, dalla sua concezione iniziale fino al suo sviluppo completo, al suo rilascio, alla sua successiva evoluzione,

Dettagli

Basi di Dati. Introduzione ai sistemi di basi di dati. K.Donno - Introduzione ai sistemi di basi di dati

Basi di Dati. Introduzione ai sistemi di basi di dati. K.Donno - Introduzione ai sistemi di basi di dati Basi di Dati Introduzione ai sistemi di basi di dati Introduzione ai sistemi di basi di dati Gestione dei Dati Una prospettiva storica File system verso DBSM Vantaggi di un DBMS Modelli dei dati Utenti

Dettagli

Introduzione ai sistemi di basi di dati

Introduzione ai sistemi di basi di dati Basi di Dati Introduzione ai sistemi di basi di dati Alessandro.bardine@gmail.com alessandro.bardine@iet.unipi.it Introduzione ai sistemi di basi di dati Gestione dei Dati Una prospettiva storica File

Dettagli

Università degli Studi di Parma Facoltà di Scienze MM. FF. NN. Corso di Laurea in Informatica. Ingegneria del Software.

Università degli Studi di Parma Facoltà di Scienze MM. FF. NN. Corso di Laurea in Informatica. Ingegneria del Software. Università degli Studi di Parma Facoltà di Scienze MM. FF. NN. Corso di Laurea in Informatica Ingegneria del Software La fase di Test Giulio Destri Ing. del Software: Test - 1 Scopo del modulo Definire

Dettagli

SVILUPPO ONTOLOGIE PER LA GESTIONE DOCUMENTALE E LORO INTEGRAZIONE ALL INTERNO DI UNA PIATTAFORMA WEB

SVILUPPO ONTOLOGIE PER LA GESTIONE DOCUMENTALE E LORO INTEGRAZIONE ALL INTERNO DI UNA PIATTAFORMA WEB Facoltà di Ingegneria Corso di Laurea Specialistica in Ingegneria Informatica SVILUPPO ONTOLOGIE PER LA GESTIONE DOCUMENTALE E LORO INTEGRAZIONE ALL INTERNO DI UNA PIATTAFORMA WEB Relatore Chiarissimo

Dettagli

Corso di Amministrazione di Sistema Parte I ITIL 2

Corso di Amministrazione di Sistema Parte I ITIL 2 Corso di Amministrazione di Sistema Parte I ITIL 2 Francesco Clabot Responsabile erogazione servizi tecnici 1 francesco.clabot@netcom-srl.it Fondamenti di ITIL per la Gestione dei Servizi Informatici IT

Dettagli

Gestione Requisiti. Ingegneria dei Requisiti. Requisito. Tipi di Requisiti e Relativi Documenti. La gestione requisiti consiste in

Gestione Requisiti. Ingegneria dei Requisiti. Requisito. Tipi di Requisiti e Relativi Documenti. La gestione requisiti consiste in Ingegneria dei Requisiti Il processo che stabilisce i servizi che il cliente richiede I requisiti sono la descrizione dei servizi del sistema Funzionalità astratte che il sistema deve fornire Le proprietà

Dettagli

TECNICO SUPERIORE PER LE APPLICAZIONI INFORMATICHE

TECNICO SUPERIORE PER LE APPLICAZIONI INFORMATICHE ISTRUZIONE E FORMAZIONE TECNICA SUPERIORE SETTORE I.C.T. Information and Communication Technology TECNICO SUPERIORE PER LE APPLICAZIONI INFORMATICHE STANDARD MINIMI DELLE COMPETENZE TECNICO PROFESSIONALI

Dettagli

Progettaz. e sviluppo Data Base

Progettaz. e sviluppo Data Base Progettaz. e sviluppo Data Base! Introduzione ai Database! Tipologie di DB (gerarchici, reticolari, relazionali, oodb) Introduzione ai database Cos è un Database Cos e un Data Base Management System (DBMS)

Dettagli

Considera tutti i requisiti funzionali (use cases) NON deve necessariamente modellare i requisiti non funzionali

Considera tutti i requisiti funzionali (use cases) NON deve necessariamente modellare i requisiti non funzionali Corso di Laurea Specialistica in Ingegneria Informatica Corso di Ingegneria del Software A. A. 2008 - Progettazione OO E. TINELLI Punto di Partenza Il modello di analisi E una rappresentazione minima del

Dettagli

Groupware e workflow

Groupware e workflow Groupware e workflow Cesare Iacobelli Introduzione Groupware e workflow sono due parole molto usate ultimamente, che, a torto o a ragione, vengono quasi sempre associate. Si moltiplicano i convegni e le

Dettagli

Metodologia Classica di Progettazione delle Basi di Dati

Metodologia Classica di Progettazione delle Basi di Dati Metodologia Classica di Progettazione delle Basi di Dati Metodologia DB 1 Due Situazioni Estreme Realtà Descritta da un documento testuale che rappresenta un insieme di requisiti del software La maggiore

Dettagli

KNOWLEDGE MANAGEMENT. Knowledge Management. Knowledge: : cos è. Dispense del corso di Gestione della Conoscenza d Impresa

KNOWLEDGE MANAGEMENT. Knowledge Management. Knowledge: : cos è. Dispense del corso di Gestione della Conoscenza d Impresa KNOWLEDGE MANAGEMENT Pasquale Lops Giovanni Semeraro Dispense del corso di Gestione della Conoscenza d Impresa 1/23 Knowledge Management La complessità crescente della società, l esubero di informazioni

Dettagli

Processo parte III. Modello Code and fix. Modello a cascata. Modello a cascata (waterfall) Leggere Sez. 7.4 Ghezzi et al.

Processo parte III. Modello Code and fix. Modello a cascata. Modello a cascata (waterfall) Leggere Sez. 7.4 Ghezzi et al. Modello Code and fix Processo parte III Leggere Sez. 7.4 Ghezzi et al. Modello iniziale Iterazione di due passi scrittura del codice correzione degli errori Problemi: dopo una serie di cambiamenti, la

Dettagli

Lezione 1. Introduzione e Modellazione Concettuale

Lezione 1. Introduzione e Modellazione Concettuale Lezione 1 Introduzione e Modellazione Concettuale 1 Tipi di Database ed Applicazioni Database Numerici e Testuali Database Multimediali Geographic Information Systems (GIS) Data Warehouses Real-time and

Dettagli

Verifica e Validazione (V & V) Software e difetti. Processo di V & V. Test

Verifica e Validazione (V & V) Software e difetti. Processo di V & V. Test Software e difetti Il software con difetti è un grande problema I difetti nel software sono comuni Come sappiamo che il software ha qualche difetto? Conosciamo tramite qualcosa, che non è il codice, cosa

Dettagli

2. Ciclo di Vita e Processi di Sviluppo

2. Ciclo di Vita e Processi di Sviluppo 2. Ciclo di Vita e Processi di Sviluppo come posso procedere nello sviluppo? Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 2. Ciclo di Vita e Processi di

Dettagli

Informatica Documentale

Informatica Documentale Informatica Documentale Ivan Scagnetto (scagnett@dimi.uniud.it) Stanza 3, Nodo Sud Dipartimento di Matematica e Informatica Via delle Scienze, n. 206 33100 Udine Tel. 0432 558451 Ricevimento: giovedì,

Dettagli

Sistemi Informativi DERIVAZIONE DEI REQUISITI FUNZIONALI. Obiettivi Specifica dei Requisiti Assembly Lines Esercizi

Sistemi Informativi DERIVAZIONE DEI REQUISITI FUNZIONALI. Obiettivi Specifica dei Requisiti Assembly Lines Esercizi Sistemi Informativi DERIVAZIONE DEI REQUISITI FUNZIONALI Obiettivi Specifica dei Requisiti Assembly Lines Esercizi Obiettivi Nelle lezioni precedenti abbiamo descritto come modellare i requisiti funzionali

Dettagli

Sistemi Informativi I Lezioni di Ingegneria del Software

Sistemi Informativi I Lezioni di Ingegneria del Software 4 Codifica, Test e Collaudo. Al termine della fase di progettazione, a volte anche in parallelo, si passa alla fase di codifica e successivamente alla fase di test e collaudo. In questa parte viene approfondita

Dettagli

I Modelli della Ricerca Operativa

I Modelli della Ricerca Operativa Capitolo 1 I Modelli della Ricerca Operativa 1.1 L approccio modellistico Il termine modello è di solito usato per indicare una costruzione artificiale realizzata per evidenziare proprietà specifiche di

Dettagli

Ingegneria del Software UML - Unified Modeling Language

Ingegneria del Software UML - Unified Modeling Language Ingegneria del Software UML - Unified Modeling Language Obiettivi. Presentare un approccio visuale alla progettazione. Illustrare i vantaggi dell utilizzo di diagrammi nella fase di progettazione. Rispondere

Dettagli

Relazione sul data warehouse e sul data mining

Relazione sul data warehouse e sul data mining Relazione sul data warehouse e sul data mining INTRODUZIONE Inquadrando il sistema informativo aziendale automatizzato come costituito dall insieme delle risorse messe a disposizione della tecnologia,

Dettagli

PIANIFICAZIONE E REALIZZAZIONE DI UN SISTEMA INFORMATIVO 147 6/001.0

PIANIFICAZIONE E REALIZZAZIONE DI UN SISTEMA INFORMATIVO 147 6/001.0 PIANIFICAZIONE E REALIZZAZIONE DI UN SISTEMA INFORMATIVO 147 6/001.0 PIANIFICAZIONE E REALIZZAZIONE DI UN SISTEMA INFORMATIVO ELEMENTI FONDAMENTALI PER LO SVILUPPO DI SISTEMI INFORMATIVI ELABORAZIONE DI

Dettagli

Introduzione alle basi di dati. Gestione delle informazioni. Gestione delle informazioni. Sistema informatico

Introduzione alle basi di dati. Gestione delle informazioni. Gestione delle informazioni. Sistema informatico Introduzione alle basi di dati Introduzione alle basi di dati Gestione delle informazioni Base di dati Modello dei dati Indipendenza dei dati Accesso ai dati Vantaggi e svantaggi dei DBMS Gestione delle

Dettagli

INGEGNERIA DEL SOFTWARE. Prof. Paolo Salvaneschi

INGEGNERIA DEL SOFTWARE. Prof. Paolo Salvaneschi Università di Bergamo Facoltà di Ingegneria Corso di Laurea in Ingegneria Informatica INGEGNERIA DEL SOFTWARE Prof. Paolo Salvaneschi 1 Obiettivi Scopi del corso: - Fornire gli elementi di base della disciplina,

Dettagli

Casi d uso (use cases)

Casi d uso (use cases) Casi d uso (use cases) proposti da Ivar Jacobson nel 1992 termine nuovo, ma tecnica consolidata (studio degli scenari di operatività degli utilizzatori di un sistema) sono i modi in cui il sistema può

Dettagli

Relazione Pinakes3 Analisi modello di business (BOZZA) di Valeriano Sandrucci 08/09/07

Relazione Pinakes3 Analisi modello di business (BOZZA) di Valeriano Sandrucci 08/09/07 Relazione Pinakes3 Analisi modello di business (BOZZA) di Valeriano Sandrucci 08/09/07 1. Introduzione...3 1.2. Application vs Tool... 3 2. Componenti logiche di un modello... 6 3. Ontologie e Semantic

Dettagli

Università degli Studi di Salerno GPS: Gestione Progetti Software. Project Proposal Versione 1.1

Università degli Studi di Salerno GPS: Gestione Progetti Software. Project Proposal Versione 1.1 Università degli Studi di Salerno GPS: Gestione Progetti Software Project Proposal Versione 1.1 Data 27/03/2009 Project Manager: D Amato Angelo 0521000698 Partecipanti: Nome Andrea Cesaro Giuseppe Russo

Dettagli

Rational Unified Process Introduzione

Rational Unified Process Introduzione Rational Unified Process Introduzione G.Raiss - A.Apolloni - 4 maggio 2001 1 Cosa è E un processo di sviluppo definito da Booch, Rumbaugh, Jacobson (autori dell Unified Modeling Language). Il RUP è un

Dettagli

Reti e sistemi informativi II Il ruolo delle IT nell organizzazione

Reti e sistemi informativi II Il ruolo delle IT nell organizzazione Reti e sistemi informativi II Il ruolo delle IT nell organizzazione Prof. Andrea Borghesan & Dr.ssa Francesca Colgato venus.unive.it/borg borg@unive.it Ricevimento: mercoledì dalle 10.00 alle 11.00 Modalità

Dettagli

Sistemi Informativi I Lezioni di Ingegneria del Software

Sistemi Informativi I Lezioni di Ingegneria del Software 1 Introduzione all Ingegneria del Software. In questa prima parte viene definita l Ingegneria del Software o Software Engineering (SWE), vengono presentate le caratteristiche del ciclo di vita di un prodotto

Dettagli

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE Allegato 1.4 Cicli di vita del software Pagina 1 di 20 Indice 1 CICLI DI VITA... 3 1.1 Ciclo di Sviluppo...3 1.2 Ciclo di Manutenzione...5 2 LE FASI PROGETTUALI...

Dettagli

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE Allegato 1.4 Cicli di vita del software Pagina 1 di 20 Indice 1 CICLI DI VITA... 3 1.1 Ciclo di Sviluppo... 3 1.2 Ciclo di Manutenzione... 5 2 LE FASI PROGETTUALI...

Dettagli

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE

ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE ALLEGATO 1.4 CICLI DI VITA DEL SOFTWARE Allegato 1.4 Cicli di vita del software Pagina 1 di 16 Indice 1 CICLI DI VITA... 3 1.1 Ciclo di Sviluppo... 3 1.2 Ciclo di Manutenzione... 5 2 LE FASI PROGETTUALI...

Dettagli

Processi di Business e Sistemi di Gestione di Workflow: concetti di base. Prof. Giancarlo Fortino g.fortino@unical.it

Processi di Business e Sistemi di Gestione di Workflow: concetti di base. Prof. Giancarlo Fortino g.fortino@unical.it Processi di Business e Sistemi di Gestione di Workflow: concetti di base Prof. Giancarlo Fortino g.fortino@unical.it Introduzione Le aziende devono modificare la loro organizzazione per cogliere le nuove

Dettagli

Principi dell ingegneria del software Relazioni fra

Principi dell ingegneria del software Relazioni fra Sommario Principi dell ingegneria del software Leggere Cap. 3 Ghezzi et al. Principi dell ingegneria del software Relazioni fra Principi Metodi e tecniche Metodologie Strumenti Descrizione dei principi

Dettagli

Corso di Ingegneria del Software Paolo Bottoni

Corso di Ingegneria del Software Paolo Bottoni Corso di Ingegneria del Software Paolo Bottoni Lezione 13: Gestione del progetto: Rischi e garanzia di qualità Obiettivi Discutere rischio e processo di gestione rischio Discutere approccio alla qualità

Dettagli

Tecniche di DM: Link analysis e Association discovery

Tecniche di DM: Link analysis e Association discovery Tecniche di DM: Link analysis e Association discovery Vincenzo Antonio Manganaro vincenzomang@virgilio.it, www.statistica.too.it Indice 1 Architettura di un generico algoritmo di DM. 2 2 Regole di associazione:

Dettagli

CONCETTI DI BASE PER LA QUALITA

CONCETTI DI BASE PER LA QUALITA CONCETTI DI BASE PER LA QUALITA Misura: è una funzione m: A -> B che associa ad ogni attributo A di un osservabile nel mondo reale o empirico (dominio) un oggetto formale B nel mondo matematico (range);

Dettagli

UML e (R)UP (an overview)

UML e (R)UP (an overview) Lo sviluppo di sistemi OO UML e (R)UP (an overview) http://www.rational.com http://www.omg.org 1 Riassumento UML E un insieme di notazioni diagrammatiche che, utilizzate congiuntamente, consentono di descrivere/modellare

Dettagli

Progettazione di un db. Basi di Dati II. Large Database. Il ruolo dei Sistemi Informativi in un azienda

Progettazione di un db. Basi di Dati II. Large Database. Il ruolo dei Sistemi Informativi in un azienda Progettazione di un db Basi di Dati II Lezione 3: Applicazioni di design & tuning di database Prof.ssa G. Tortora a.a. 2004/2005 1 Abbiamo già visto in dettaglio gli aspetti teorici di progettazione di

Dettagli

Rischi 1. Definizione di rischio nello sviluppo del software Analisi dei rischi Gestione dei rischi

Rischi 1. Definizione di rischio nello sviluppo del software Analisi dei rischi Gestione dei rischi Rischi 1 Definizione di rischio nello sviluppo del software Analisi dei rischi Gestione dei rischi Un ingegnere del software viene coinvolto direttamente nel processo di identificazione delle aree potenziali

Dettagli

WebRatio. L altra strada per il BPM. Web Models s.r.l. www.webratio.com contact@webratio.com 1 / 8

WebRatio. L altra strada per il BPM. Web Models s.r.l. www.webratio.com contact@webratio.com 1 / 8 WebRatio L altra strada per il BPM Web Models s.r.l. www.webratio.com contact@webratio.com 1 / 8 Il BPM Il BPM (Business Process Management) non è solo una tecnologia, ma più a grandi linee una disciplina

Dettagli

ANALISI DI UN CASO DI EVOLUZIONE NELL ADOZIONE DELLA SOLUZIONE PROJECT AND PORTFOLIO MANAGEMENT DI HP.

ANALISI DI UN CASO DI EVOLUZIONE NELL ADOZIONE DELLA SOLUZIONE PROJECT AND PORTFOLIO MANAGEMENT DI HP. INTERVISTA 13 settembre 2012 ANALISI DI UN CASO DI EVOLUZIONE NELL ADOZIONE DELLA SOLUZIONE PROJECT AND PORTFOLIO MANAGEMENT DI HP. Intervista ad Ermanno Pappalardo, Lead Solution Consultant HP Software

Dettagli

Ciclo di vita dimensionale

Ciclo di vita dimensionale aprile 2012 1 Il ciclo di vita dimensionale Business Dimensional Lifecycle, chiamato anche Kimball Lifecycle descrive il framework complessivo che lega le diverse attività dello sviluppo di un sistema

Dettagli

Tecnopolis CSATA s.c.r.l. APQ in Materia di Ricerca Scientifica nella Regione Puglia

Tecnopolis CSATA s.c.r.l. APQ in Materia di Ricerca Scientifica nella Regione Puglia BANDO ACQUISIZIONI Prodotti Software ALLEGATO 6.3 Capitolato Tecnico Piattaforma per l Analisi e la Progettazione di alto livello del Software Allegato 6.3: capitolato tecnico Pag. 1 1 Ambiente di Analisi

Dettagli

Ciclo di Vita Evolutivo

Ciclo di Vita Evolutivo Ciclo di Vita Evolutivo Prof.ssa Enrica Gentile a.a. 2011-2012 Modello del ciclo di vita Stabiliti gli obiettivi ed i requisiti Si procede: All analisi del sistema nella sua interezza Alla progettazione

Dettagli

Architetture Web. parte 1. Programmazione in Ambienti Distribuiti A.A. 2003-04

Architetture Web. parte 1. Programmazione in Ambienti Distribuiti A.A. 2003-04 Architetture Web parte 1 Programmazione in Ambienti Distribuiti A.A. 2003-04 Architetture Web (1) Modello a tre livelli in cui le interazioni tra livello presentazione e livello applicazione sono mediate

Dettagli

Corso di Amministrazione di Sistema Parte I ITIL 3

Corso di Amministrazione di Sistema Parte I ITIL 3 Corso di Amministrazione di Sistema Parte I ITIL 3 Francesco Clabot Responsabile erogazione servizi tecnici 1 francesco.clabot@netcom-srl.it Fondamenti di ITIL per la Gestione dei Servizi Informatici Il

Dettagli

E.T.L. (Extract.Tansform.Load) IBM - ISeries 1/8

E.T.L. (Extract.Tansform.Load) IBM - ISeries 1/8 E.T.L. (Extract.Tansform.Load) IBM - ISeries Quick-EDD/ DR-DRm ETL 1/8 Sommario ETL... 3 I processi ETL (Extraction, Transformation and Loading - estrazione, trasformazione e caricamento)... 3 Cos è l

Dettagli

4. Requisiti del Software

4. Requisiti del Software 4. Requisiti del Software Cosa? Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 4. Requisiti del Software 1 / 35 Sommario 1 Generalità 2 Categorizzazione

Dettagli

Ciclo di vita del progetto

Ciclo di vita del progetto IT Project Management Lezione 2 Ciclo di vita del progetto Federica Spiga A.A. 2009-2010 1 Ciclo di vita del progetto Il ciclo di vita del progetto definisce le fasi che collegano l inizio e la fine del

Dettagli

Ciclo di vita del software

Ciclo di vita del software Ciclo di vita del software Nel corso degli anni, nel passaggio dalla visione artigianale alla visione industriale del software, si è compreso che il processo andava formalizzato attraverso: un insieme

Dettagli

Cos è l Ingegneria del Software?

Cos è l Ingegneria del Software? Cos è l Ingegneria del Software? Corpus di metodologie e tecniche per la produzione di sistemi software. L ingegneria del software è la disciplina tecnologica e gestionale che riguarda la produzione sistematica

Dettagli

Lezione 10 Business Process Modeling

Lezione 10 Business Process Modeling Lezione 10 Business Process Modeling Ingegneria dei Processi Aziendali Modulo 1 - Servizi Web Unità didattica 1 Protocolli Web Ernesto Damiani Università di Milano Step dell evoluzione del business process

Dettagli

Requisiti e Specifica

Requisiti e Specifica Università di Bergamo Dipartimento di Ingegneria gestionale, dell'informazione e della produzione INGEGNERIA DEL SOFTWARE Paolo Salvaneschi A3_2 V3.2 Requisiti e Specifica Tecniche e linguaggi Il contenuto

Dettagli

Concetti di base di ingegneria del software

Concetti di base di ingegneria del software Concetti di base di ingegneria del software [Dalle dispense del corso «Ingegneria del software» del prof. A. Furfaro (UNICAL)] Principali qualità del software Correttezza Affidabilità Robustezza Efficienza

Dettagli

Sillabo. REQB Certified Professional for Requirements Engineering. Livello Foundation

Sillabo. REQB Certified Professional for Requirements Engineering. Livello Foundation Sillabo REQB Certified Professional for Livello Foundation Version 2.1 2014 I diritti di autore (copyright) di questa edizione del Sillabo, sono di REQB e ITA-STQB Storia delle modifiche Versione Data

Dettagli

Lezione 1 Ingegneria del Software II- Introduzione e Motivazione. Ingegneria del Software 2 Introduzione e Richiami 1

Lezione 1 Ingegneria del Software II- Introduzione e Motivazione. Ingegneria del Software 2 Introduzione e Richiami 1 Lezione 1 Ingegneria del Software II- Introduzione e Motivazione Ingegneria del Software 2 Introduzione e Richiami 1 Riferimenti bibliografici I. Sommerville Ingegneria del Software 8a edizione Cap.1 R.

Dettagli

I Valori del Manifesto Agile sono direttamente applicabili a Scrum:!

I Valori del Manifesto Agile sono direttamente applicabili a Scrum:! Scrum descrizione I Principi di Scrum I Valori dal Manifesto Agile Scrum è il framework Agile più noto. E la sorgente di molte delle idee che si trovano oggi nei Principi e nei Valori del Manifesto Agile,

Dettagli

ARIES. Architettura per l'implementazione rapida dei Sistemi Aziendali. Presentazione della metodologia ARIES

ARIES. Architettura per l'implementazione rapida dei Sistemi Aziendali. Presentazione della metodologia ARIES ARIES Architettura per l'implementazione rapida dei Sistemi Aziendali. Presentazione della metodologia ARIES ARIES è una metodologia per implementare rapidamente sistemi informativi aziendali complessi,

Dettagli

Release Management. Obiettivi. Definizioni. Responsabilità. Attività. Input

Release Management. Obiettivi. Definizioni. Responsabilità. Attività. Input Release Management Obiettivi Obiettivo del Release Management è di raggiungere una visione d insieme del cambiamento nei servizi IT e accertarsi che tutti gli aspetti di una release (tecnici e non) siano

Dettagli

Verifica e Validazione del Simulatore

Verifica e Validazione del Simulatore Verifica e del Simulatore I 4 passi principali del processo simulativo Formulare ed analizzare il problema Sviluppare il Modello del Sistema Raccolta e/o Stima dati per caratterizzare l uso del Modello

Dettagli

Introdurre i dati di produzione nel testing prestazionale

Introdurre i dati di produzione nel testing prestazionale Business white paper Introdurre i dati di produzione nel testing prestazionale Il valore del testing continuativo delle prestazioni applicative Le problematiche più comuni nei test prestazionali Nel corso

Dettagli

TECNICO SUPERIORE PER IL SISTEMA INFORMATIVO AZIENDALE

TECNICO SUPERIORE PER IL SISTEMA INFORMATIVO AZIENDALE ISTRUZIONE E FORMAZIONE TECNICA SUPERIORE SETTORE INDUSTRIA E ARTIGIANATO TECNICO SUPERIORE PER IL SISTEMA INFORMATIVO AZIENDALE STANDARD MINIMI DELLE COMPETENZE TECNICO PROFESSIONALI DESCRIZIONE DELLA

Dettagli

piattaforma comune miglioramento del processo adeguamento all anno 2000 sistemi legacy obsoleti visibilità dei dati standardizzazione di più sedi

piattaforma comune miglioramento del processo adeguamento all anno 2000 sistemi legacy obsoleti visibilità dei dati standardizzazione di più sedi AO automazioneoggi Facciamo un po d ordine L approccio ai progetti di implementazione di sistemi ERP è spesso poco strutturato e il processo di implementazione risulta essere frequentemente inefficiente

Dettagli

Il linguaggio per la moderna progettazione dei processi aziendali

Il linguaggio per la moderna progettazione dei processi aziendali Il linguaggio per la moderna progettazione dei processi aziendali Organizzare un azienda sotto il profilo dei processi è oramai diventata una disciplina a cavallo tra la competenza aziendalistica ed informatica.

Dettagli

Configuration Management

Configuration Management Configuration Management Obiettivi Obiettivo del Configuration Management è di fornire un modello logico dell infrastruttura informatica identificando, controllando, mantenendo e verificando le versioni

Dettagli

La disciplina che cura un approccio sistematico, disciplinato e quantificabile allo sviluppo, all operatività ed alla manutenzione del software

La disciplina che cura un approccio sistematico, disciplinato e quantificabile allo sviluppo, all operatività ed alla manutenzione del software Ingegneria del software (software engineering) La branca dell'ingegneria che si occupa della realizzazione di sistemi software. La disciplina che cura un approccio sistematico, disciplinato e quantificabile

Dettagli

Software project management. www.vincenzocalabro.it

Software project management. www.vincenzocalabro.it Software project management Software project management Sono le attività necessarie per assicurare che un prodotto software sia sviluppato rispettando le scadenze fissate rispondendo a determinati standard

Dettagli

Le Basi di dati: generalità. Unità di Apprendimento A1 1

Le Basi di dati: generalità. Unità di Apprendimento A1 1 Le Basi di dati: generalità Unità di Apprendimento A1 1 1 Cosa è una base di dati In ogni modello di organizzazione della vita dell uomo vengono trattate informazioni Una volta individuate e raccolte devono

Dettagli

Analisi dei Requisiti e Specifica

Analisi dei Requisiti e Specifica Università di Bergamo Facoltà di Ingegneria INGEGNERIA DEL SOFTWARE Paolo Salvaneschi A3_2 V2.1 Analisi dei Requisiti e Specifica Tecniche e linguaggi Il contenuto del documento è liberamente utilizzabile

Dettagli

ARIES. Architettura per l implementazione rapida dei sistemi aziendali

ARIES. Architettura per l implementazione rapida dei sistemi aziendali ARIES Architettura per l implementazione rapida dei sistemi aziendali P r e s e n ta z i o n e d e l l a m e t o d o l o g i a a r i e s ARIES è una metodologia che consente di implementare rapidamente

Dettagli

Gestire un progetto di introduzione di sistemi informativi di SCM. 1 Marco Bettucci Gestione della produzione II - LIUC

Gestire un progetto di introduzione di sistemi informativi di SCM. 1 Marco Bettucci Gestione della produzione II - LIUC Gestire un progetto di introduzione di sistemi informativi di SCM 1 Che cos è un progetto? Una serie complessa di attività in un intervallo temporale definito... finalizzate al raggiungimento di obiettivi

Dettagli

Sistemi Informativi Aziendali I

Sistemi Informativi Aziendali I Modulo 3 Sistemi Informativi Aziendali I 1 Corso Sistemi Informativi Aziendali I - Modulo 3 Modulo 3 Costruire i Sistemi Informativi d Impresa: Decidere il Sistema d Impresa; Progettare il Sistema d Impresa;

Dettagli

La modellazione tecnico produttiva: dall approccio modulare al configuratore

La modellazione tecnico produttiva: dall approccio modulare al configuratore M. Germani, M. Mengoni: Corso di Gestione documentale tecnica di prodotto La modellazione tecnico produttiva: dall approccio modulare al configuratore Per poter configurare tecnicamente il prodotto è necessario

Dettagli

Progetto. Struttura del documento di specifica dei requisiti, Casi d uso. manuel.comparetti@iet.unipi.it

Progetto. Struttura del documento di specifica dei requisiti, Casi d uso. manuel.comparetti@iet.unipi.it Progetto Struttura del documento di specifica dei requisiti, Casi d uso manuel.comparetti@iet.unipi.it 1 Documenti da produrre Il progetto deve comprendere i seguenti documenti: Documento di specifica

Dettagli

7. Architetture Software

7. Architetture Software 7. Architetture Software progettare la struttura Andrea Polini Ingegneria del Software Corso di Laurea in Informatica (Ingegneria del Software) 7. Architetture Software 1 / 20 Scopo della fase di design

Dettagli

ANALISI E PROGETTAZIONE OBJECT ORIENTED. Lorenzo Saladini

ANALISI E PROGETTAZIONE OBJECT ORIENTED. Lorenzo Saladini ANALISI E PROGETTAZIONE OBJECT ORIENTED Lorenzo Saladini 1. Introduzione In questo capitolo vengono presentati alcuni degli elementi necessari al corretto sviluppo di sistemi informatici secondo una metodologia

Dettagli

Utilizzo Gestione dei file

Utilizzo Gestione dei file Utilizzo Gestione dei file Info su questo bollettino tecnico L'intento di questo bollettino tecnico è di aiutare gli sviluppatori FileMaker esperti a comprendere meglio e ad applicare le migliori metodologie

Dettagli

Progetto software 2008/2009. Docente Marianna Nicolosi Asmundo

Progetto software 2008/2009. Docente Marianna Nicolosi Asmundo Progetto software 2008/2009 Docente Marianna Nicolosi Asmundo Obiettivi del corso Coinvolgervi nello sviluppo di un progetto software in cui mettere a frutto le conoscenze che avete acquisito durante i

Dettagli

02: Project Management

02: Project Management 02: Project Management Le tre P del project management Persone motivate / esperte SEI PM-CMM (People Management Capability Maturity Model) assunzione / selezione addestramento / cultura di gruppo stipendio

Dettagli

Corso di Access. Prerequisiti. Modulo L2A (Access) 1.1 Concetti di base. Utilizzo elementare del computer Concetti fondamentali di basi di dati

Corso di Access. Prerequisiti. Modulo L2A (Access) 1.1 Concetti di base. Utilizzo elementare del computer Concetti fondamentali di basi di dati Corso di Access Modulo L2A (Access) 1.1 Concetti di base 1 Prerequisiti Utilizzo elementare del computer Concetti fondamentali di basi di dati 2 1 Introduzione Un ambiente DBMS è un applicazione che consente

Dettagli

Introduzione. Il software e l ingegneria del software. Marina Mongiello Ingegneria del software 1

Introduzione. Il software e l ingegneria del software. Marina Mongiello Ingegneria del software 1 Introduzione Il software e l ingegneria del software Marina Mongiello Ingegneria del software 1 Sommario Il software L ingegneria del software Fasi del ciclo di vita del software Pianificazione di sistema

Dettagli

I Costi Occulti della Migrazione dei Dati

I Costi Occulti della Migrazione dei Dati I Costi Occulti della Migrazione dei Dati Brett Callow Copyright Acronis, Inc., 2000 2008 Quando si sostituiscono o consolidano i sistemi e si incontrano esigenze in continua crescita rispetto alle capacità,

Dettagli

Capitolo 2. Un introduzione all analisi dinamica dei sistemi

Capitolo 2. Un introduzione all analisi dinamica dei sistemi Capitolo 2 Un introduzione all analisi dinamica dei sistemi Obiettivo: presentare una modellistica di applicazione generale per l analisi delle caratteristiche dinamiche di sistemi, nota come system dynamics,

Dettagli

Piano di gestione della qualità

Piano di gestione della qualità Piano di gestione della qualità Pianificazione della qualità Politica ed obiettivi della qualità Riferimento ad un eventuale modello di qualità adottato Controllo della qualità Procedure di controllo.

Dettagli