Sei sulla pagina 1di 24

INGEGNERIA

DEL SOFTWARE
2
Indice 3

Parte I
Definizione d’ingegneria del software, Ciclo di vita 4
Qualità e Principi 5
Disegno e Modularizzazione 7
Software concorrente e distribuito 8
Architetture 9

Parte II
Analisi dei Requisiti: Modello concettuale e Casi d’uso 10
Specifica, Invarianti OCL, Contratti (pre, post) 12
UML 13
Diagrammi di classe (uso e associazioni) 14
Diagrammi di package, sequenza, stato, attività 16

Parte III
La verifica nel processo di produzione 19
Verifica della correttezza e dell’affidabilità 20
Classificazione delle tecniche di test (testing vs. debugging) 20
Teoria del testing 21
Test in piccolo (scatola trasperente e nera, oracolo) 22
Testi in grande (test di modulo, sistema, integrazione) 23
Verifica analitica (cenni) 24

3
PARTE 1

Definizione d’ingegneria del software

È quella disciplina che si occupa dei processi produttivi e delle metodologie di sviluppo finalizzate alla realizzazione di
sistemi software. È l’applicazione di teorie, metodi e strumenti nella progettazione e produzione di software, tenendo
conto di vincoli organizzativi e finanziari. Quindi riguarda lo sviluppo, la messa in opera, e lamanutenzione del software.
Mentre un programmatore scrive un programma completo, un ingegnere del software progetta (e/o scrive) una
componente software, da combinare con altre componenti, eventualmente scritte da altri, nella costruzione di un sistema.
La programmazione è primariamente un’attività individuale, mentre l’ingegneria del software è un’attività di gruppo.
Ciò che è stato appena detto rappresenta il punto di vista interno all’ingegneria del software. È implicito infatti che il
software viene prodotto per essere venduto o, comunque, utilizzato; in quanto tale, deve soddisfare un punto di vista
esterno, e quindi soddisfare i requisiti, cioè le richieste dell’utente o del mercato. Per questo l’ingegneria del software ha
sviluppato metodi per l’analisi dei requisiti.
Riassumendo, l’ingegneria del software fornisce un procedimento sistematico, disciplinato e quantificabile, che contempli
le varie fasi del ciclo di vita del software, nella produzione di componenti software (ri)utilizzabili nello sviluppo di sistemi
tenendo conto di costi, pianificazione ed organizzazione del lavoro di un team e delle esigenze del cliente o del mercato.

Ciclo di vita di un software

Il software è un prodotto immateriale, nasce da esigenze specifiche, deve essere prodotto con costi e tempi certi, ed è
soggetto a manutenzione. L’immaterialità del software rende irrilevante la produzione materiale, e quindi il processo di
sviluppo del codice è il processo produttivo stesso. Le fasi di tale processo sono indicate con il termine ciclo di vita.
Il ciclo di vita è composto da varie fasi. Nel modello tradizionale chiamato modello a cascata ogni fase ha un inizio e una
fine ben definiti. Raramente però la realtà è così semplice, perché processi di retroazione non sono eliminabili. Il
vantaggio di tale modello è avere una chiara individuazione delle fasi, e per ogni fase la caratterizzazione di un metodo
specifico. Le fasi sono:
1 - analisi e specifica dei requisiti : questa è la prima fase di sviluppo e ha luogo dopo uno studio di fattibilità che ha
lo scopo di stabilire costi e benefici; lo scopo è quello di identificare i requisiti e quindi richiede l’interazione con l’utente
e l’analisi di tutte le esigenze. I requisiti dovrebbero essere scritti impiegando un linguaggio comprensibile all’utente
finale.
2 - disegno e specifica del sistema : una volta stilati i requisiti bisogna individuare le unità del sistema; questa fase è
caratterizzata dalle domande “che cosa” (che cosa sarà contenuto nel sistema) e “come” (come realizzarlo); in realtà
questa fase di divide in 2 fasi: disegno architetturale, in cui si definisce l’organizzazione complessiva del sistema con
unitià di alto livello, e disegno dettagliato, con la definizione dei moduli di basso livello e le interfacce; in quest’ultima
parte si stabiliscono il comportamento e la specifica di ogni modulo. Perciò si procede in ordine di “raffinamento.
3 - codifica e verifica dei moduli : una volta prodotte la documentazione e la specifica di ogni modulo, bisogna scrivere
il codice; in questa fase l’ingegnere produce il codice che si troverà nella versione rilasciata all’utente finale. Anche in altre
fasi si scrive codice (ad esempio test), ma solo in questa c’è il codice che servirà all’utente finale.
4 - integrazione e verifica del sistema : una volta codificate e testate tutte le unità bisogna integrarle per ottenere un
sistema funzionante, in una fase di assemblaggio e test sul sistema finale.
5 - rilascio e manutenzione : una volta codificato tutto, e testato il sistema, si rilascia il prodotto finale all’utente, e si
entra in una fase di manutenzione, cioè una fase di eventuali modifiche, basate sulla documentazione precedentemente
rilasciata.

4
Qualità

Nell’ingegneria tradizionale le qualità di un prodotto sono cosa distinta dalla progettazione dello stesso; nell’ingegneria
del software questa distinzione diventa sfumata, la qualità del software non è indipendente dalla qualità della sua
specificazione e progettazione (o disegno). Il raggiungimento di queste qualità rappresenta il fine dell’ingegneria del
software.

Tipi di qualità:
- esterna : quella percepibile “eseguendo” il software
- interna : quella percepibile dagli sviluppatori
- in uso : esterna, percepita “usando” il software (utente)
- del prodotto : valuta il prodotto indipendentemente dal processo di produzione
- del processo : valuta il processo di produzione, in base a criteri quali costi e produttività o a criteri quali la
capacità di portare a prodotti di qualità.

Qualità del software:

Le prime 5 sono comportamentali. Le prime 3 sono strettamente collegate, in quanto stabiliscono se il software produce
la funzionalità attesa; per cui sono 3 qualità del prodotto, che possono essere interne o esterne.
1 - correttezza : un software è funzionalmente corretto se si comporta secondo quanto stabilito dalle specifiche
funzionali, documentate nella fase di analisi dei requisiti. Si verifica la correttezza tramite un test (verifica sperimentale) e
con dimostrazione matematica (verifica matematica).
2 - affidabilità : un software è affidabile se l’utente può fare affidamento sulle sue funzionalità. La correttezza è una
qualità assoluta (0,1), mentre l’affidabilità è relativa (senza una misurazione precisa). In generale, se la conseguenza di un
errore software non è grave, l’utente può comunque considerare il software affidabile.
3 - robustezza : un software è robusto se si comporta in modo accettabile anche in circostanze non previste nell’analisi
dei requisiti, per esempio quando vengono inseriti input non corretti o se ci sono malfunzionamenti hardware. Notiamo
che se un imprevisto è specificato nell’analisi dei requisiti, allora esso diventa un requisito di correttezza, e non più di
robustezza.
4 - usabilità : un software è usabile se gli utenti lo ritengono facile da utilizzare. È una qualità molto soggettiva. Dipende
dall’attrattività (interfaccia grafica), la comprensibilità, l’operabilità, la prevedibilità (in base alle standardizzazioni) e
l’esperienza dell’utente. Un prodotto molto standard è un prodotto molto usabile.
5 - sicurezza : un software è sicuro in rapporto alla sua capacità di prevenire accessi non autorizzati e resistere agli
attacchi. È una qualità esterna e del prodotto.

6 - prestazioni : è una qualità particolare che riguarda la performance di prodotto. Le prestazioni sono importanti, infatti
se un sistema software è troppo lento potrebbe ridurre la percezione positiva dell’utente, magari fino al punto da non
soddisfare più le esigenze primarie. “Prestazioni” ed “Efficienza” sono due caratteristiche distinte: la prima è una qualità
esterna, percepita dall’utente, la seconda è una qualità interna che si riferisce alla capacità del software di sfruttare le
risorse del computer.
7 - manutenibilità : è una qualità che riguarda la fase successiva di manutenzione. Se un software è manutenibile allora
diminuisce il costo di manutenzione, ma aumenta il costo di produzione; il costo complessivo però è minore. È una
qualità interna del prodotto. Sottoqualità:
- riparabilità : è la facilità di correggere gli errori (modularità);
- modificabilità : facilità di migliorare il sistema (modularità): più si evolve meno è modificabile;
- testabilità : facilità di testare le modifiche.

Il legacy software (software ereditato) è un termine che si riferisce a un software utilizzato all’interno di un’organizzazione
per molto tempo, dovuto a investimenti passati. Può capitare che sia scritto in un linguaggio di programmazione vecchio,
pertanto è difficile da modificare. Si usano tecniche di reengineering per ristrutturare il legacy software.

Le prossime 3 qualità riguardano l’utilizzabilità del software in diversi contesti.


8 – riusabilità : un software è riusabile quando si presta al riutilizzo in diversi contesti (o una parte di codice da utilizzare
in un altro progetto); poiché è inutile riscrivere un algoritmo noto o un programma in libreria, questa qualità è
importante ma non è scontata da garantire. È una qualità relativa più ai moduli che al sistema, ed è una qualità del
prodotto: se è intesa come interna è relativa a un modulo utilizzato per il sistema stesso in versioni successive; se è intesa
come esterna è relativa a un modulo utilizzato per un altro sistema.
9 – portabilità : è la capacità di un software di esssere eseguito in ambienti diversi (piattaforme hardware e sistemi
operativi). La modularità è ancora una volta importante, perché le dipendenza dall’ambiente vengono isolate in pochi

5
moduli, modificabili in caso di trasporto. È una qualità interna che riguarda l’intero sistema, e non i moduli, come
accadeva per la riusabilità.
10 – interoperabilità : è la capacità di un sistema di coesistere e cooperare con altri sistemi. È una qualità interna che
riguarda l’intero sistema. La riusabilità può essere intesa come l’interoperabilità di un modulo; mentre l’interoperabilità è
una qualità del sistema.

Le prossime 2 qualità riguardano la bontà del codice e del progetto.


11 – verificabilità : è una qualità a supporto delle altre qualità, come la correttezza, l’affidabilità e le prestazioni; serve
per confrontare il software con la specifiche della documentazione. È una proprietà interna. Un software verificabile ha un
codice ben scritto.
12 – comprensibilità : un software è comprensibile se ha un codice ben scritto e un progetto (disegno) ben delineato.
Come la verificabilità, ha ripercussioni su altre qualità, come la correttezza, la manutenzione, e la verificabilità stessa.

Le ultime 3 qualità sono dette qualità del processo di produzione. Quindi riguardano il personale.
13 – produttività : capacità di produzione del software, dovuta al rapporto tra quantità di lavoro e impegno destinato. È
difficile da misurare.
14 – tempestività : capacità di rendere disponibile un prodotto nei tempi richiesti. Le difficoltà sono l’evoluzione delle
richieste e i tempi stretti.
15 – visibiltà : un processo è visibile se tutti i passi (step) successivi e lo stato corrente sono documentati con chiarezza.
Tale processo si dice che è trasparente.

Principi

I principi sono regole generali, da seguire nel processo di sviluppo per ottenere le qualità. Alla base di questo processo ci
sono i principi, i quali portano a metodi & tecniche, che insieme fanno le metologie, sulle quali si basano gli strumenti
di progettazione. Un metodo è un insieme di linee guida generali, una tecnica è un insieme di linee guida specifiche, una
metologia è l’insieme di un metodo e una relativa tecnica.
1 – Rigore e formalità : seguire un procedimento disciplinato e rigoroso con metodi formali. Questo perché se il
procedimento è lasciato all’inventiva è difficilmente ripetibile, comunicabile e controllabile, quindi risulta inaffidabile.
Rendere il procedimento formale migliora la verificabilità matematica, e quindi si utilizzano linguaggi e semantica formali.
2 – Separazione : trattare separatemente problemi diversi. Se il sistema è di grandi dimesioni, in questo modo è più
facile da analizzare. Separare quindi problemi relativi al prodotto, al processo, problemi economici, trattare
separatamente qualità diverse.
3 – Modularità : è una particolarizzazione della separazione: decomporre il sistema in parti coese e di basso
accoppiamento. Decomporre significa procedere top-down (divide et impera), quindi dividere ricorsivamente il problema
in sottoproblemi facili. Viceversa., il riuso dei moduli prevede un comportamento bottom-up, quindi riutilizzando moduli
già conosciuti, che risolvono determinati problemi. Aiuta la modificabilità.
La modularità prevede altri 2 sottoprincipi: l’alta coesione (gli elementi di un modulo devono avere lo stesso scopo, o
scopi collegati) e il basso accoppiamento (due moduli devono essere indipendenti).
4 – Astrazione : astrarre dettagli non rilevanti rispetto allo scopo. Serve a migliorare le compresione e l’analisi di un
sistema.
5 – Generalità : controllare se un problema è un caso particolare di uno già risolto. In questo modo si evita la costruzione
di moduli che risolvono due varianti dello stesso problema.
6 – Incrementalità : procedere per incrementi, in modo da rilasciare passo passo una versione sempre più aggiornata del
software; questo perché i requisiti potrebbero non essere corretti all’inizio, e si mantiene un feedback dall’utente.
7 – Anticipazione dei cambiamenti : prevedere le possibili modifiche. Migliora la modificabilità. Poiché non tutti i
cambiamenti sono prevedibili, esistono dei tipi di cambiamenti tipici:
- cambio di algoritmi, strutture dati (migliorare l’efficienza), ambiente software (sis.op, linguaggio di prog.), periferiche, e
ambiente sociale (il quale non dipende dall tecnologia, ma da eventi esterni, come le leggi).

6
Disegno (progettazione)

I principi fondamentali da seguire durante la fase di analisi dei requisiti sono modularità, anticipazione dei cambiamenti e
rigore.
Dopo l’analisi e specifica dei requisiti inizia la fase di progettazione (disegno). Il disegno (progetto) decompone un
sistema in parti, assegna le responsabilità ad ogni parte e garantisce che le parti collaborino fra loro per ottenere il
risultato desiderato. Le parti sono i moduli e vengono man mano raffinate, e le responsabilità sono le funzioni individuate
nella fase di analisi; è utile distinguere fra disegno architetturale e disegno dettagliato.
Il disegno architetturale decide l’architettura complessiva del sistema, cioè individua le componenti più “grosse”,
descrive le principali componenti e la logica che ha portato all’individuazione di quelle componenti, le relazioni fra le
componenti.
Il disegno dettagliato è la decomposizione del sistema in moduli che indichi quali sono i compiti di ogni modulo, quali
sono le relazioni fra i moduli, tiene conto delle decisioni prese a livello architetturale, e può esser vista come la fase di
implementazione dell’architettura.

Modularizzazione: Tecniche e Relazioni

Applicare il principio di modularità significa individuare i moduli e le relazioni fra di essi. Un modulo è un’unità software
anche complessa, che fornisce un insieme di servizi (risorse) computazionali. Le relazioni forniscono l’aspetto strutturale
della decomposizione.
Sia S un sistema composto dai moduli M1, M2,…Mn, allora una
relazione r su S è un sottoinsieme di SxS. Quindi dati Mi e Mj
appartenenti a S, la coppia (Mi Mj) è dovuta alla relazione r.
Una relazione è gerarchica se e solo se non esistono cicli nel
grafo della relazione; questo tipo di grafo è detto DAG (directed
acyclic graph).
Si possono individuare vari tipi di relazioni, corrispondenti a vari
aspetti che si vogliono analizzare; se siamo interessati ad
evidenziare in primo luogo gli aspetti architetturali, emergono
due tipi principali di relazioni: “usa” e “componente di”.

- La relazione “usa” : M usa N se N contiene risorse necessarie ad M per svolgere il suo compito; si dice anche che M è
cliente di N o che N è server di M. normalmente “usa” è un DAG, per cui è possibile individuare dei livelli.
La relazione “usa” fornisce informazioni sulla struttura modulare di un’architettura:
fan out = numero di archi di uscita da un modulo: misura il grado di accoppiamento; si cerca di tenerlo basso;
fan in = numero di archi di ingresso di un modulo: misura l’utilizzo dello stesso.
- La relazione “componente di” :
Un modulo M di alto livello può essere espanso in sottomoduli che lo dettagliano. Chiameremo questi sottomoduli
componenti di M. La relazione “componente di” consente di tracciare il processo di raffinamento.

Modularizzazione: Interfaccia e Information hiding

Per definire veramente l’interazione tra due moduli occorre definire un’interfaccia. Un’interfaccia rappresenta tutto ciò
che è necessario sapere per utilizzare un modulo M, quindi i servizi offerti da M e quelli richiesti necessari per il suo
funzionamento. Siccome è l’unica parte del modulo che è necessario conoscere per decidere se e come utilizzarlo,
l’interfaccia deve essere specificata in modo preciso e rigoroso.
Inoltre si definisce anche l’incapsulamento del modulo, o information hiding: è l’altra faccia della medaglia, cioè
mentre l’interfaccia rende pubblico tutto ciò che serve per comprendere il modo d’uso del modulo, il modulo incapsula e
nasconde tutto il resto. Il meccanismo interfaccia-information hiding consente di gestire il cambiamento, infatti si può
cambiare la parte nascosta senza che i clienti se ne accorgano, pur di mantenere le stesse interfacce.

Per decidere cosa pubblicare nell’interfaccia bisogna si tratta di applicare adeguatamente il principio di astrazione. Come
ogni astrazione, l’interfaccia dipende dallo scopo: deve trascurare tutti i dettagli irrilevanti rispetto allo scopo (restano
nascosti nel modulo) e pubblicare solo quelli essenziali.

7
Software concorrente

Si parla di software concorrente quando ci sono diversi attività eseguite in concorrenza. Uno dei problemi principali in
questi casi è la coerenza dei dati condivisi tra i moduli. Due attività concorrenti procedono in parallelo finchè le loro
azioni non interferiscono. Se devono competere per l’accesso di una risorsa condivisa non possono più procedere
indipendentemente. Per questo bisogna assicurare la mutua esclusione.
Per garantire la mutua esclusione ci sono diversi modi. Uno di questi è l’uso di monitor: un monitor è un oggetto
astratto cui si può accedere in un ambiente concorrente; se un processo P richiede l’uso di un monitor già impegnato, il
monitor sospende P, finchè non si libera.
Un'altra tecnica usata per la mutua esclusione si chiama rendezvous: le attività si suddividono in due categorie, cioè i
processi veri e propri e i “guardians” delle risorse condivise; essi sono task che non terminano e ciclicamente aspettano
una richiesta ad una risorsa condivisa, che possono rifiutare o accettare; il processo che si vede rifituare la richiesta
rimane in sospeso finchè il guardian non accetta.

Software distribuito

Un software distribuito è un particolare software concorrente, in cui le attività concorrenti si trovano su macchine
differenti connesse da una rete di comunicazione, come ad esempio una LAN. In questa situazione ci sono 3 nuove
questioni:
- i vincoli modulo-macchina : potrebbe essere rischiesto che un modulo sia eseguito su una determinata macchiana (ad
esempio se un modulo deve stampare, bisogna farlo su un macchina collegata alla stampante);
- la comunicazione tra moduli : due moduli che sono su macchine differenti comunicano attraverso un meccanismo
speciale di chiamata di procedure che si chiama RPC (remote procedure call), oppure si comunica attraverso l’uso di
messaggi;
- accesso efficiente agli oggetti astratti : gli oggetti astratti sono classificati come moduli; in un sistema distribuito la
comunicazione tra due moduli che sono su diverse macchine richiede molto tempo (RCP), perciò esistono due metodi
per velocizzare l’accesso: il primo è la replicazione, cioè replicare l’oggetto su diverse macchine o addirittura su tutte
quante; il secondo è la distribuzione, cioè partizionare l’oggetto sulle diverse macchine anche se dal punto di vista
logico rimane un unico oggetto, mettendo la partizione opportuna vicino ai clienti che presumibilmente ne avranno
bisogno.

8
Architetture

L’architettura di sistema è la struttura delle parti che compongono un’installazione completa, l’individuazione delle
responsabilità delle parti e le interconnessioni, più eventualmente la tecnologia di base.
L’architettura di componenti è l’insieme di componenti software di livello applicativo, le loro relazioni e le dipendenze
che influiscono sul loro comportamento.
Nella progettazione software si parla anche di architettura di disegno, cioè l’insieme dei moduli software che realizzano
le componenti, relazioni strutturali fra di essi e le interfacce.
Ci sono due gruppi di architetture di sistema: generali e legate ai domini specifici.

Tra le architetture generali distinguiamo:


- pipeline: i sottosistemi sono organizzati in modo da formare una pipeline, in cui ogni sottosistema prende in input
l’output del sottosistema precedente, e in particolare il primo sottosistema prende l’input dell’intero sistema e l’ultimo
sottosistema restituisce l’output dell’intero sistema;

- blackboard: se i vari sottosistemi debbono comunicare gli uni con gli altri, può essere appropriata un’architettura a
blackboard, in cui uno dei sottosistemi è eletto a “lavagna” e serve come mezzo di comunicazione tra gli altri
sottosistemi;

- basata su eventi: sono tipiche dei sistemi interattivi; gli eventi sono generati da qualcosa di esterno(un utente, un
segnale da un sensore, un messaggio dalla rete…); in questo caso le varie componenti sono slegate le une dalle altre e
rispondono al verificarsi di determinati eventi.

Le architetture legate ai domini specifici prevedono architetture specifiche, come ad esempio MVC e Client-Server.
MVC (Model View Control) è composta da 3 componenti separate: il modello del mondo reale, la visualizzazione che
mostra il modello all’utente, e il controllore che comunica con l’utente e controlla le altre 2 componenti.

9
PARTE 2

Analisi dei requisiti

Ogni iterazione è costituita dalle fasi


– Analisi dei requisiti
– Specifica
– Disegno

L’analisi dei requisiti ha in uscita le specifiche dei requisiti, punto di inizio delle fasi di specifica e disegno.
Nella programmazione ad oggetti i punti principali dell’analisi sono:
– Le funzioni del sistema : X è una funzione del sistema se possiamo dire che il sistema fa X
– I concetti del dominio di problema
– I processi legati all’utilizzo del sistema, organizzati in casi d’uso (processi d’uso tipici) e descritti in modo testuale e/o
mediante diagrammi.

Modello concettuale

Nella parte finale dell’analisi dei requisiti si fa un Modello concettuale:


Si fa un’analisi concettuale consiste nella stesura di due elementi:
- glossario: è un elenco di termini denotanti concetti, per organizzare e descrivere gli aspetti individuati in fase di analisi.
Ogni concetto consiste di un termine, una descrizione e una categoria (classe, attributo, funzione…)
- diagrammi di classe: mostrano le classi concettuali individuate e le relazioni (associazioni, generalizzazione, …) fra di
esse.

EoD (Esercizi o Domande)

10
Casi d’uso

Un caso d’uso è un documento narrativo che descrive le sequenze di eventi di un attore che usa un sistema per
completare un processo. Nell’analisi dei casi d’uso si individuano i confini del sistema (cioò che è interno al sistema, ed
esterno) e gli attori (categorie di persone o cose).
Un caso d’uso descrive un processo di interazione tipico di uno o più attori con il sistema. La descrizione può essere
essenziale (processo di astrazione) o reale (mostra tutti i passaggi).
I casi d’uso sono classificati in Primari, Secondari e Opzionali.
Il rango di un caso d’uso (alto, medio, basso) denota la sua importanza: più è alto più va trattato prima.

L’individuazione e descrizione dei casi d’uso essenziali fa parte dell’analisi dei requisiti; ogni sviluppo di un caso d’uso
reale fa partire un’iterazione, con disegno e specifiche di disegno.

Caso d’uso Essenziale

Caso d’uso Reale

11
La specifica

Nell’intero processo di sviluppo, le specifiche costituiscono un elemento centrale, a partire dalla fase iniziale di
individuazione dei requisiti , fino alle fasi implementativa e di manutenzione. E’ utile distinguere fra:
– Specifica dei requisiti: specificano il comportamento del sistema rispetto a quanto richiesto
- funzioni del sistema
- modello concettuale (glossario, diagrammi di classe)
- casi d’uso
– Specifica di disegno: questo è il livello tipico dei diagrammi di classe, di package e di componente
– Specifica dei moduli: Specificano le interfacce generali dei singoli moduli o delle singole classi

Utilità delle specifiche:


- comprendere e precisare i requisiti dell’utente
- specificare l’interfaccia fra sistema ed esterno
- implementare in base ai requisiti
- fornire precisi punti di riferimento per le modifiche

Qualità delle specifiche:


- Comprensibilità, chiarezza, non ambiguità:
- Consistenza (coerenza, senza contraddizioni)
- Completezza

Stili di specifica:
- Operazionale: descrive e indica come il sistema raggiunge uno scopo
- Descrittivo: descrive e indica cosa il sistema deve fare, non come
- Informale: linguaggio naturale
- Formale: linguaggio formale
- Semi formale: linguaggio semi formale come UML

Vincoli (invarianti) e contratti


Dato un diagramma di classe, un’istanza del diagramma di classe è un diagramma di oggetti. Un vincolo è un predicato
che restituisce un valore booleano. Un’istanza di un diagramma rispetta il vincolo se se il vincolo su quell’istanza è True.

OCL (Object Constraint Language) è il linguaggio formale di UML per la scrittura di vincoli (invarianti, contratti).

PRE/POST
Un contratto è composto da una pre-condizione (obbligo di
partenza) e una post-condizione (stabilisce un obbligo finale)

12
UML

UML è un linguaggio di modellazione orientato agli oggetti (OO). È un linguaggio grafico. Usa diversi tipi di diagrammi
per modellare diversi aspetti. UML è un formalismo aperto ad estensioni e personalizzazioni: uno strumento per eseguire
queste estensioni sono gli stereotipi. Sintassi stereotipi: << … >>
• Es. <<interface>>

UML usa diversi tipi di diagrammi per modellare diversi aspetti:


• Diagrammi dei casi d’uso, per modellare i requisiti;
• Diagrammi di classe per modellare gli aspetti statici di un modello ed in particolare quelli architetturali;
• Diagrammi di interazione, per modellare gli aspetti collaborazione dinamica fra oggetti.

Inoltre possiamo classificare i diagrammi in due grandi categorie:


• Diagrammi statici: oggetti, classe, package, componente, deployment, vincoli;
• Diagrammi dinamici: sono principalmente uno strumento di specifica e verranno considerati a tale livello.

UML viene usato per implementare le relazioni (vedi modularizzazione):


- “è componente di”: nei diagrammi di deployment, componente e package;
- “usa”: in UML viene “raffinata” in più relazioni:
– generalizzazione : A generalizza B se B può esser visto come caso particolare di A
– realizzazione : A realizza B se A contiene le risorse per realizzare quanto specificato in B
– dipendenza : A dipende da B se modifiche in B possono avere influenza su A
– associazione

13
Diagrammi di classe

Il diagramma di classe illustra l’ambito di descrizione da un punto di vista statico, evidenziandone in particolare
caratteristiche e mutue relazioni. Gli elementi fondamentali sono le classi e le relazioni. Vengono usati nell’analisi dei
requisiti e per le specifiche dichiarative (descrittive).

Classe

Una classe descrive un insieme di entità (oggetti) dotate delle stesse caratteristiche e proprietà, ed è rappresentata
graficamente da un rettangolo, contenente il nome della classe, gli attributi e i metodi. Un attributo descrive una
caratteristica propria di ogni istanza (oggetto) della classe, e può avere diverse visibilità: + (public), - (private), # (protect);
inoltre si indica il tipo dell’attributo. Es: +nome : String. Il tipo indicato insieme al metodo è il tipo restituito. Esempio di
metodo: +getNome() : String.

La rappresentazione classe-oggetto è basata sulla semantica type-instance, tipica di UML, dove type è la classe e
instance è l’oggetto.

Classe astratta

Una classe astratta è una classe che non può essere istanziata direttamente; contiene una serie di attributi e metodi.
L’implementazione dei metodi è propria delle classi che implementano la classe astratta. Graficamente una classe astratta
viene indicata scrivendone il nome in corsivo. Anche i metodi astratti sono scritti in corsivo.

Interfaccia

Un’interfaccia è una classe che non ha implementazione: presenta infatti solo metodi. Le interfacce possono essere
fornite o richieste, e s’indicano:

Uso dei diagrammi di classe

I diagrammi di classe si usano nell’analisi dei requisiti (modello concettuale) e nella progettazione (disegno dettagliato).
Durante la fase di progettazione vengono usati per la relazione “usa”, la quale viene distinta in 4 tipi:
- Generalizzazione
- Realizzazione
- Dipendenza
- Associazione
La relazione “componente di” non è collegata ai diagrammi di classe, ma ai diagrammi di package.

14
Associazioni (Diagrammi di classe)

Cos’è un’associazione
Le associazioni sono relazioni fra classi e rappresentano legami di varia natura fra oggetti di tali classi. Le classi si
istanziano in oggetti e le associazioni in link fra oggetti. Le associazioni vengono ereditate, come metodi e attributi.

Navigazione e Ruolo
Le associazioni possono avere una direzione di navigazione: se la navigazione è da A a B solo gli oggetti di A possono
mandare messaggi agli oggetti di B e non viceversa. Agli estremi della navigazione si indicano direzione ed
eventualmente ruolo (ruolo della classe nell’associazione).

Vincoli di molteplicità
Vincolano l’insieme delle possibili istanze di un diagramma di classe.

Associazione di composizione
Un oggetto non può appartenere a due composti diversi; i componenti non esistono senza il composto.

Associazione “parte di”


Non ha vincoli particolari (un oggetto può essere parti di due oggetti diversi); si usa per modellare l’essere parte di senza
essere componente.

Associazione qualificate
Corrispondono all’associazione tra indici di un oggetto e altri oggetti.

Classi associative
È una classe legata ad una particolare associazione: descrive informazioni proprie dell’associazione e non delle classi
connesse a quest’ultima. Questo è utile perchè i link potrebbe avere bisogno di attributi. (Es. di classe associativa: Strada).

15
Diagrammi di package

Una classe appartiene ad un package A se compare nel package A stesso oppure in un package contenuto di A.
L’elemento principale del diagramma è il package. Un package contiene un numero arbitrario di elementi UML e
permette di trattarli come entità unica. Il package ha un nome identificativo scritto all’interno, oppure scritto nel quadrato
superiore nel caso si voglia rappresentare alcune classi interne. Tali diagrammi modellano relazioni “componente di”
oppure relazioni d’uso di generalizzazione e dipendenza.

Diagrammi di sequenza

I diagrammi di sequenza, stato e attività descrivono le specifiche operazionali (spiegano il come, il funzionamento). Il
diagramma di sequenza descrive l’interazione tra diversi elementi come una sequenza temporale di azioni ed eventi. Gli
elementi principali del diagramma sono gli oggetti: ogni oggetto è un rettangolo da cui parte una linea tratteggiata
verso il basso che rappresenta la sua “vita” (in senso temporale); per indicare che un oggetto è attivo, la linea tratteggiata
diventa una barra solida (di stacking), detta linea di attivazione. Ogni oggetto può essere creato o distrutto da un altro
oggetto.

16
Diagrammi di stato

Il diagramma di stato rappresenta un oggetto attraverso la descrizione dei diversi stati in cui può venirsi a trovare.
L’elemento principale è quindi lo stato, cioè una possibile configurazione o situazione dell’oggetto. L’altro elemento è
l’evento, che è un accadimento che può far scattare una transizione di stato; esso può essere esterno (utente) o interno
(eccezione, ad esempio divisione per zero). Ad ogni evento è associata una condizione, che se verificata causa l’azione
(l’evento stesso). L’insieme evento[condizion]/azione si chiama transizione.
Inoltre ad ogni evento sono associati anche un sender (genera l’evento) e un receiver (tratta l’evento); esistono 4 tipi di
eventi:
- Call Events o eventi sincroni : il sender sospende la propria attività finchè non riceve una risposta;
- Signals o eventi asincroni : il sender prosegue la propria attività indipendentemente dal receiver;
- Time Events : eventi basati sullo scorrere del tempo;
- Change Events : eventi basati sulla modifica di uno stato.

Ogni stato è definito da un Nome e delle Attività interne eseguite ogni volta che l’oggetto si trova nello stato.
Esistono 3 tipi di stato:
- stato semplice : stato contenente le sole attivitò proprie (come visto finora);
- superstato : contiene altri stati che sono legati tra loro attraverso transizioni o comportamenti simili;
- stato concorrente : contiene altri stati validi contemporaneamente, che quindi lavorano concorrentemente (esempio
Produttore-Buffer-Consumatore).

17
Diagrammi di attività

Il diagramma di attività descrive il flusso di azioni necessarie per eseguire una determinata procedura. Quindi servono a
modellare gli aspetti dinamici di un sistema o di una parte di esso. Le attività interne ad uno stato sono un caso
particolare di diagramma di attività.
Mentre nei diagrammi di stato le transizioni scattano in seguito all’occorrenza di un evento, in un diagramma di attività
una transizione non scatta in seguito ad eventi ma avviene al completamento di un’attività.

Si definisce Workflow il flusso di attività dal punto di vista degli attori che collaborano con il sistema.
È possibile modellare il flusso delle attività attraverso 3 elementi, che formano la struttura di controllo:

1 - Branch : rami seguiti dal verificarsi di condizioni (a seconda della condizione si verifica un solo ramo)

2 - Fork : avvio di attività parallele

3 - Join : sincronizzazione di attività parallele

Esempio

18
PARTE 3 - VERIFICA

Iniziamo con il dire che bisogna verificare un prodotto software e il suo processo di sviluppo.
Con programmi di piccole dimensioni la verifica spesso consiste nel verificare che i risultati prodotti dal codice in
esecuzione soddisfino le aspettative, questo non va bene per prodotti software complessi: verificare il software solo dopo
averlo prodotto renderebbe difficile l'eliminazione dei difetti, cioè sarebbe difficile sapere quale parte del codice è
inesatta. Quindi l'attività di verifica deve procedere secondo principi rigorosi.

La verifica nel processo di produzione

Riprendiamo in esame le fasi del processo di produzione del software, per collocare la verifica in esso:

I. Studio di fattibilità
Scopo: Identificare il problema e prevedere gli scenari futuri nello sviluppo del sistema
Output: definizione del problema, soluzione alternative e benefici attesi, risorse necessarie (costi, date di rilascio per ogni
alternativa)

II. Analisi e specificazione dei requisiti


Scopo: identificare comprendere e specificare i requisiti, pianificare e controllare la qualità del processo.
Output: documento di specifica dei requisiti che deve essere comprensibile, rigoroso, completo, consistente, non
ambiguo e modificabile, con:
Requisiti funzionali (vedi lezioni )
Requisiti non funzionali: sicurezza, prestazioni, interfaccia uomo-macchina, vincoli real time, ecc.
Requisiti del processo di produzione e manutenzione:
controllo della qualità : procedure di testing e validazione
manutenibilità: procedure di manutenzione;

III. Disegno (progettazione)


Scopo: identificare i moduli e le relazioni fra essi
Output: specifica dell’architettura di sistema e del software, a vari livelli; raffinamento dei procedimenti di verifica e di
testing.

IV Codifica e verifica dei moduli


Scopo: produrre i moduli del sistema a partire da specifiche precise e verificare / testare i moduli.
Output: i moduli e la relativa documentazione, la documentazione relativa alla verifiche/test, impalcatura di test (per test
di non regressione)

V. Integrazione e testing del sistema


Scopo: Integrare i moduli in un (sotto)sistema funzionante e testare il funzionamento del sistema a fronte dei requisiti
Output: (sotto)sistema funzionante e relativa documentazione e documentazione relativa al testing di sistema (alfa-
testing)

VI. Rilascio e manutenzione


Scopo: rilasciare il sistema (si effettua ulteriore verifica), manutenzione correttiva e perfettiva
Output: deployment, beta-testing (per il rilascio), documentazione,……… la vita dopo il rilascio ……….

La verifica è un’attività trasversale che riguarda tutte le fasi a partire dalla verifica delle specificazioni dei requisiti, alla
verifica dei singoli moduli sino alla verifica del sistema integrato.
Inoltre anche tutte le qualità debbono essere verificate: correttezza, affidabilità, robustezza, prestazioni, portabilità,
interoperabilità, riusabilità, ecc.

19
Verifica della correttezza e dell’affidabilità

Eseguire una verifica dei moduli ha lo scopo di garantire la correttezza del codice rispetto alla specifica del modulo.
Viene eseguita quando la documentazione che specifica in dettaglio i moduli del sistema e le loro interfacce è stata
prodotta (il codice dei moduli è stato prodotto a partire da specifiche dettagliate e precise).

Eseguire una verifica del sistema ha lo scopo di garantire l'affidabilità del sistema: cioè il grado di correttezza rispetto alle
specifiche dei requisiti e l'aderenza alle aspettative del cliente (significa anche verifica della adeguatezza delle stesse
specifiche)

Classificazione delle tecniche di test

Distinguiamo fra tecniche:


empiriche: testing, consiste nel sperimentare il comportamento di un prodotto per analizzare se questo si comporti
secondo secondo le aspettative
analitiche: si analizza il prodotto e tutta la documentazione di progetto, per ricavare indicazioni circa la correttezza del
suo operare. Si fa quindi :ispezione del codice, analisi statica, verifica di correttezza, ecc

Test empirico

Il testing può mostrare la presenza di errori (bugs), mai la loro assenza (Dijkstra).
E' il metodo più naturale per verificare un software e consiste nel provarlo in un certo numero di situazione
rappresentative, accertando che si comporti come previsto.

Es. di testing non accurato (specifiche precise e complete consentono una maggior accuratezza):
Specifica di z = x+2 imprecisa: z > x. Test con ingresso x=5 e uscita z=8: OK (non accurato)
Specifica precisa: z = x+2. Test con ingresso x=5 e uscita z=8: BUG (accurato)

Differenza tra testing e debugging

Testing: capacità di rilevare la presenza di un errore. Debugging: capacità di localizzare l’errore.


Un buon metodo di testing deve supportare, nei limiti del possibile, il debugging:
È però esperienza comune che il fatto che il debugging è spesso complicato e richiede l’ausilio di altri test mirati, di
tracciare variabili o stack, ecc.

20
Teoria del testing

Data una porzione di codice P si definisce:


D = dominio di dati in ingresso
R = range di dati in uscita

Una specifica è una coppia:


Pre(x) determina il dominio D dei dati di “ingresso”
Post(x,y), con x in D e y in R: con input x, P deve fornire un output y tale che: Post(x,y) sia soddisfatta;

Definizione di casi di test e fallimento


Un caso di test è un input c in D (si noti che verifica la precondizione).
La presenza di un errore viene dimostrata mostrando che P(d) è un risultato scorretto per qualche D e cioè P(d) non
soddisfa i requisiti. Definiamo questa situazione un fallimento.

Definizione di insieme di test


Un insieme di test è un insieme di casi di test.
Un insieme di test T ha successo se tutti i casi di test di T hanno successo (non falliscono); se T non ha successo, fallisce.
T è detto ideale se, posto che esista un errore in P, T fallisce, cioè esiste un caso di test c in T che fallisce
Ovviamente se P è corretto ogni insieme di test è ideale; ma se non è corretto, può accadere che non esistano insiemi di
test ideali finiti.

Un criterio di test C specifica una condizione che deve essere soddisfatta da un insieme di test.

Un criterio di test C è consistente (rispetto a P) se, presi due test T1, T2 che soddisfano C, T1 ha successo se e solo se T2
ha successo (non si contraddicono).
Un criterio C è completo se vi è almeno un insieme ideale di test T che soddisfa C.
Se C è consistente e completo, allora ogni insieme di test che verifica C è un insieme di test ideale.
Ovviamente non si hanno criteri di test consistenti e completi (che ottengono test ideali) ma si tratta di approssimare al
meglio tali proprietà “ideali” per ottenere test significativi.

Riassumendo: Il testing consite nel progettare e costruire insiemi di test che filtrino (speribilmente) gli errori
usando opportuni criteri di test.

Esempio: fattoriale approssimato.


Funzione fattoriale(x:Integer):Real

– pre nonNeg: x >= 0


– post esatto: 0 <= x < 20 -> result = x!
– post approx: 20 <= x <= 30 -> 0,9*x! < result < 1,1*x!
– post err: x < 0 or x > 30 -> Exception

La specifica porta a considerare un criterio basato sulla partizione del dominio D nei seguenti casi d’errore:

E1 = {0 <= x <= 20}


E2 = {20 < x <= 30}
E3 = {x < 0 or x > 30}

Un insieme di test per tale criterio è {x = -1, x = 3, x = 29}


Chiaramente, se P supera questo insieme di test, non è necessariamente corretto; ci si aspetta che lo sia
Un raffinamento ragionevole è:

E1 = {0 <= x <= 20}


E2 = {20 < x <= 30}
E3 = {x < 0}
E4 = {x > 30}

21
Test in piccolo

Il test in piccolo affronta il test per singolo modulo. Esistono 2 approcci principali:
test a scatola trasparente: nel determinare il criterio di test, si considera il codice del modulo (a volte persino ignorando
la specifica)
test a scatola nera: si ignora la struttura interna del modulo, e si determina il criterio di test in base alla specifica

Test a scatola trasparente

La prima e più semplice strategia suggerisce di scegliere i casi di test in modo da eseguire tutte le istruzioni del
programma.
Questo criterio viene chiamato criterio della copertura delle istruzioni: ogni istruzione è esercitata almeno una volta da
almeno un caso di test; si basa sull'osservazione che non è possibile individuare un errore se la parte del programma che
contiene l'errore non viene eseguita.

Associamo un grafo di controllo a un frammento di programma. Definiamo il criterio di copertura degli archi: ogni arco
è percorso almeno una volta da almeno un caso di test.

L'attraversamento dei singoli archi può non esser sufficiente per assicurare il percorso di tutti i flussi di esecuzione
importanti del programma. Si è arrivate dunque al criterio di copertura dei cammini: ogni cammino è percorso
almeno una volta da almeno un caso di test.
Riassumendo: Il criterio di copertura degli archi raffina quello di copertura delle istruzioni, quello dei cammini
raffina quello degli archi.

Test a scatola nera

E' guidato dalle specifiche, è detto anche test funzionale. Se si considera il modulo come una scatola nera, è solo
possibile testare il suo funzionamento a fronte delle specifiche.
Le specifiche diventano la sorgente dei criteri di test.

La scelta di un criterio {Ei} si basa sull’assunzione che i casi di una stessa sorgente d’errore Ei si comportino allo stesso
modo.
Tale assunzione non sempre è ragionevole e può portare a criteri poco significativi.
Un esempio tipico in cui l’assunzione porta a risultati poco affidabili è costituito dagli errori di confine.
Ad es. spesso si scrive < al posto di <=, ecc.

Siccome gli errori di confine sono frequenti, si associa ad un criterio:


il criterio di copertura dei confini che richiede di considerare come sorgenti d’errore i valori di confine dei diversi Ei,
secondo un’individuazione dei confini, per lo più in base al naturale ordinamento dei dati.
Si usa sia nel test con scatola nera, sia nel test con scatola trasparente.

Oracolo

Una volta fissato un criterio, si tratta di stabilire se un caso di test fallisce o meno.
Anche questo è un problema privo di un metodo generale di decisione, già sul piano teorico si dice che è necessario un
oracolo.
Sul piano pratico il problema può essere non banale: il programma di controllo di un robot non può essere testato
direttamente sul robot, un errore può causare danni al robot.
In questo caso l’oracolo è una simulazione del robot e dell’ambiente in cui opera.
È utile implementare un oracolo, dove possibile: oracolo implementato = programma che, a fronte dell’esecuzione di un
caso di test, risponde: OK, FALLIMENTO, NON SO.

22
Test in grande

Il test in piccolo riguarda la progettazione di criteri di test per piccole porzioni di codice (ad es. singoli metodi o
addirittura porzioni di codice critiche). Per i moduli e per l’intero sistema la progettazione dei casi di test non segue criteri
quali la copertura delle condizioni o di tutti gli archi, per problemi di esplosione combinatoria (cioè perdersi in
innumerevoli calcoli).
Per governare la complessità, la soluzione è , al solito, applicare i principi di modularità e separazione.

La modularizzazione dell’attività di testing dovrebbe riflettere quella di disegno. Ciò significa che l'architettura modulare
del sistema è un naturale candidato per guidare la verifica del sistema stesso.
Allo stesso modo in cui costruiamo sistemi complessi come una collezione di moduli, analogamente dobbiamo essere in
grado di testare i moduli separatamente, uno per uno, e successivamente testare l'intero sistema.

Distingueremo fra: test dei moduli, test di integrazione e test di sistema.

Test di modulo

Viene fatto per verificare che il modulo sia stato implementato correttamente rispetto al suo comportamento atteso.

Bisogna però stare attenti che spesso i moduli non lavorano come entità isolate, quindi a volte non possono essere
eseguiti da soli perchè:
 il modulo usa un'operazione che non fa parte del modulio
 il modulo accede a variabili non locali
 il modulo è esso stesso chiamato da più moduli
Quindi occorre simulare tutte queste situazioni per rendere possibile il test del modulo.

Se il modulo deve chiamare una funzione che ad es. non è stata ancora creata, il modo più semplice per gestire la
chiamata è quello di costruire uno stub, vale a dire una funzione che ha gli stessi parametri di I/O della funzione
mancante, ma con un comportamento semplificato.
Ovviamente è anche necessario costruire un driver. Un driver è un programma che simula l'invocazione del modulo sotto
test. Il driver inizializza i valori delle variabili condivise sostituendosi a ciò che moduli esterni farebbero nel caso reale.

23
Test di sistema

Intende verificare se una collezione intera di moduli funzioni correttamente (rispetto alle specifiche), assumendo che i
singoli moduli abbiamo un comportamento accettabile.
Però il termine sistema deve essere definito in modo adeguato in base al contesto cioè ad es.:
 Sistema da integrare in un sistema software preesistente: simile al test di un modulo in un ambiente in cui la
parte rimanente è già implementata
 Sistema di controllo: possibile necessità di un ambiente di simulazione
 Sistema interattivo: testare l’usabilità, interazione con un’organizzazione umana (es. ufficio)

Nel test di sistema si eseguono altri 3 test:


 test di sovraccarico: i requisiti richiedono che un sistema operi in determinato regime (ad es., N transazioni per
secondo con ritardo medio di risposta T). Anche se la possibilità di sovraccarico non è stata prevista nei requisiti,
può essere individuata in fasi successive: il sistema sarà testato anche in condizioni di sovraccarico.
 test di robustezza: determinati eventi eccezionali non sono stati previsti nei requisiti, ma vengono individuati in
fasi successive: vanno considerati nei test di robustezza.
 test di (non)regressione: certe qualità, inclusa la correttezza, possono regredire in seguito agli interventi nella
fase di manutenzione evolutiva. E' utile individuare e mantenere in vita test critici rispetto alle possibilità di
degrado;

Test d’integrazione

I sistemi vengono spesso integrati in modo graduale: si mettono insieme collezioni di moduli per formare sottoinsiemi e
a questi vengono progressivamente integrati altri moduli o altri sottoinsiemi. I sistemi che sono costruiti attraverso
progressive integrazioni di componenti vengono testati con il test di integrazione. Integrazione incrementale localizza gli
errori: integrazione di N moduli già testata fa capire che l'errore probabilmente è nella collaborazione con N+1.

Importante: nello stadio finale dell'integrazione si ottiene l'intero sistema e, pertanto, il test di integrazione viene a
coincidere con il test di sistema.

Verifica analitica (cenni)

Il test richiede l’esecuzione sperimentale del codice.


L’analisi invece esamina il codice senza eseguirlo, ha i seguenti vantaggi rispetto al testing:
 studia proprietà che valgono su tutte (o su classi di) le esecuzioni, e non su singole esecuzioni

e i seguenti svantaggi (problemi):


 fallibilità del ragionamento
 uso di modelli che possono astrarre da sorgenti di errore

24