Is Riassunto
Is Riassunto
La storia dell'ingegneria del software inizia alla fine degli anni '50, quando i computer
divennero più accessibili grazie alla diminuzione dei costi. L'introduzione dei linguaggi di
alto livello semplificò la comunicazione con le macchine, portando alla nascita della
professione di "programmatore". Tuttavia, la separazione tra utente e programmatore
portò a problemi di comprensione delle intenzioni dell'utente nella creazione del software.
Negli anni '60, si tentò di sviluppare grandi sistemi software commerciali, ma ciò portò alla
"crisi del software" a causa delle difficoltà nel coordinare il lavoro di diverse squadre di
programmatori e nel gestire la documentazione. In risposta a questa situazione, durante
una conferenza della NATO nel 1968, fu coniato il termine "ingegneria del software",
sottolineando la necessità di un approccio sistematico all'attività di sviluppo.
2
I fattori di qualità del software possono essere suddivisi in due categorie: qualità interne e
qualità esterne. Le qualità esterne sono visibili agli utenti e riguardano il prodotto software
come una "black box", mentre le qualità interne sono focalizzate sugli sviluppatori e
vedono il software come una "white box".
Diversi portatori di interesse (stakeholders) hanno interesse nel sistema software, tra cui
l'utente, il mercato, il cliente, l'organizzazione produttrice, il finanziatore, il legislatore e
altri. Ogni requisito del software dovrebbe essere associato a uno o più portatori di
interesse.
La classificazione delle qualità del software comprende sia la qualità del prodotto che la
qualità del processo. Le qualità interne influenzano quelle esterne, poiché consentono agli
sviluppatori di raggiungere gli obiettivi di qualità esterna. Allo stesso modo, la qualità del
processo, inclusa la competenza e l'esperienza delle persone coinvolte, influisce sulla
qualità del prodotto finale.
3
I principi dell'ingegneria del software includono:
4
La modularità nell'ingegneria del software è il processo di scomposizione del sistema in
parti più piccole e ben definite. Questo viene fatto per dominare la complessità del sistema
e affrontare in modo separato gli aspetti diversi del problema. La modularità ci permette di
concentrarci su ciascuna parte del sistema in maniera indipendente dalle altre,
semplificando così il processo di sviluppo.
Ci sono problemi complessi con soluzioni semplici e problemi semplici con soluzioni
complesse o addirittura senza soluzione. Prima di affrontare la complessità, occorre
comprendere il concetto di calcolabilità, che riguarda la possibilità di risolvere un problema
tramite un algoritmo realizzabile con una macchina di Turing.
Nell'ambito della modularità, vengono utilizzati concetti come algoritmi, problemi calcolabili
e non calcolabili e la codifica dei programmi. Gli algoritmi possono essere implementati
con linguaggi di programmazione, e la codifica dei programmi rappresenta il modo in cui
descriviamo la soluzione a un problema.
5
La modellazione a oggetti è una fase di analisi nel processo di ingegneria del software, in
cui l'input è una descrizione informale del problema e l'output sono i requisiti scritti in
maniera formale, utilizzando il linguaggio UML. Durante questa fase, si distinguono i
requisiti sui dati, i requisiti funzionali e quelli non funzionali (come prestazioni, sicurezza,
affidabilità e sicurezza).
I requisiti sui dati permettono di individuare le entità che fanno parte del dominio del
problema, concentrandosi su ciò che sono indipendentemente dalle operazioni possibili su
di esse. I requisiti funzionali, invece, definiscono le funzionalità del software da realizzare,
coinvolgendo più entità.
In C++, un puntatore alla classe base può puntare legittimamente a un oggetto della
classe derivata, ma non viceversa. Il late binding, che permette il polimorfismo dinamico,
avviene tramite un puntatore all'oggetto che punta alla tabella dei metodi virtuali della
classe, consentendo una ricerca tabellare della funzione da invocare a tempo di
esecuzione.
6
Il diagramma dei casi d'uso in UML viene utilizzato per modellare le funzionalità del
sistema software. Ogni caso d'uso rappresenta un'interazione tipica tra un attore e il
sistema, al fine di svolgere un'unità di lavoro utile. Gli attori possono essere persone
fisiche, altri sistemi software, dispositivi hardware esterni o il tempo stesso.
• Attori: Sono le entità esterne al sistema che interagiscono direttamente con esso.
• Casi d'uso: Rappresentano le funzionalità del sistema, descritte da ovali con una
breve stringa che le identifica.
• Relazioni tra attori e casi d'uso: Collegano gli attori con i casi d'uso che essi
innestano.
• Confini del sistema: Definiscono cosa fa parte del sistema (all'interno dei confini) e
cosa ne è escluso (fuori dai confini).
Le relazioni tra attori e casi d'uso possono essere di diversi tipi:
• Relazioni di generalizzazione tra attori o casi d'uso, che permettono di astrarre ruoli
comuni e semplificare i diagrammi.
• Relazioni di inclusione, dove più casi d'uso racchiudono una sequenza di passi
comuni, evitando di specificarli ripetutamente.
• Relazioni d'estensione, che descrivono sequenze opzionali aggiunte al caso d'uso
in determinate circostanze.
Ogni caso d'uso è documentato con una scheda che descrive la sequenza di eventi
principali, le precondizioni, le postcondizioni e le sequenze di eventi alternativi. Gli scenari
rappresentano diversi percorsi di esecuzione del caso d'uso, incluso lo scenario principale
(senza errori o deviazioni) e, se necessario, scenari secondari per situazioni in cui
qualcosa non procede come previsto.
I diagrammi dei casi d'uso sono utili per la comunicazione tra analisti, progettisti,
committenti e altri stakeholders, poiché offrono una rappresentazione grafica delle
funzionalità del sistema, facilmente comprensibile anche per le persone non esperte.
Inoltre, aiutano nella validazione dei requisiti, permettendo di confrontarli con le aspettative
degli stakeholders e correggere eventuali errori fin dalle fasi iniziali dello sviluppo del
software.
7
Il diagramma delle classi viene utilizzato per modellare le entità e le relazioni tra di esse in
un sistema software. Durante l'analisi, il focus è sul dominio del problema, mentre durante
la progettazione, si modellano le entità del sistema. Il diagramma delle classi è uno
strumento statico che offre una vista concettuale delle entità e delle loro caratteristiche,
senza descrivere come verranno trattate o implementate.
Le classi sono rappresentate attraverso rettangoli con il nome nella parte superiore, le
proprietà (attributi) nel centro e le responsabilità (operazioni) nella parte inferiore. I legami
tra le classi possono essere di tre tipi: generalizzazione/specializzazione (è-un), tutto/parti
(o contenimento) e associazione.
Nella fase di analisi, i legami rappresentano i fatti della realtà e non hanno direzionalità,
mentre in fase di progettazione, possono essere introdotte direzionalità per riflettere
requisiti funzionali specifici.
Le classi boundary e control sono stereotipi che indicano il confine tra l'utente e il sistema.
Le classi boundary rappresentano le interfacce tra gli attori primari e i casi d'uso, mentre le
classi control gestiscono le operazioni e le interazioni tra le entità. In alcuni casi, una
classe boundary può comunicare direttamente con le entità, mentre in situazioni più
complesse, può interagire con una classe control che gestisce le operazioni più complesse
coinvolgendo diverse classi.
Il diagramma delle classi aiuta a garantire la consistenza tra il modello delle classi e il
diagramma dei casi d'uso, poiché le operazioni delle classi boundary corrispondono ai casi
d'uso attivati dagli attori primari.
Inoltre, durante la programmazione, il diagramma delle classi può essere utilizzato come
riferimento per la codifica delle classi in linguaggio di programmazione, consentendo di
tradurre il modello in codice in modo più immediato.
In sintesi, il diagramma delle classi offre una visione statica delle entità e delle loro
relazioni nel sistema, fornendo una base solida per il processo di progettazione e sviluppo
del software.
8
Il testo fornisce una panoramica sull'importanza e il processo dell'ingegneria dei requisiti
nel ciclo di vita del software. Ecco un riassunto dei punti chiave:
La fase di ingegneria dei requisiti comprende diverse attività, come l'esplicitazione dei
requisiti, l'analisi e la negoziazione, la documentazione e la validazione.
I requisiti devono essere chiari, non ambigui, testabili, completi e coerenti per evitare
problemi nelle fasi successive dello sviluppo.
Un difetto (fault) può portare a un errore (error), che a sua volta può generare una failure,
ovvero un comportamento inadeguato del sistema.
La tracciabilità dei requisiti è cruciale per gestire i cambiamenti e gli impatti sulle attività e
sul progetto.
L'ingegneria dei requisiti continua a essere importante in tutte le fasi dello sviluppo del
software, fino al collaudo e al testing.
Garantire una buona modularità è essenziale per gestire i cambiamenti nei requisiti in
modo efficiente.
L'efficienza del sistema informatico è legata al suo rendimento, che rappresenta il rapporto
tra il lavoro utile e il lavoro totale.
In generale, un'adeguata ingegneria dei requisiti riduce i costi, migliora la qualità del
software e soddisfa meglio le esigenze degli utenti.
9
I diagrammi di interazione sono uno dei tipi di diagrammi UML utilizzati per aggiungere la
dinamica comportamentale agli oggetti individuati nel modello delle classi. Questi
diagrammi descrivono gli scenari dei casi d'uso attraverso le interazioni tra le entità, che
sono rappresentate come istanze delle classi nel diagramma delle classi.
Nei diagrammi di comunicazione, invece, le linee di vita degli oggetti sono disposte
orizzontalmente, e le interazioni tra gli oggetti sono rappresentate da frecce con numeri di
sequenza che indicano l'ordine temporale degli eventi. Questi diagrammi enfatizzano gli
effetti sugli oggetti coinvolti e rendono più espliciti gli scambi di messaggi tra di essi. Sono
spesso utilizzati per validare il diagramma delle classi e comprendere le relazioni strutturali
tra le entità.
L'obiettivo dei diagrammi di interazione è catturare gli scenari dei casi d'uso e definire
come gli oggetti comunicano tra loro per soddisfare i requisiti del sistema. Questa
visualizzazione dinamica aiuta a comprendere il comportamento del software e può
rivelare la necessità di ulteriori classi non ancora specificate nel diagramma delle classi,
portando a eventuali modifiche nel modello.
10
I diagrammi di stato sono un tipo di diagrammi UML utilizzati per descrivere il
comportamento dinamico di un sistema o di un oggetto. Introducono il concetto di stato
come una condizione di funzionamento, in cui l'oggetto risponderà con una specifica
uscita in base all'ingresso ricevuto e allo stato in cui si trova.
Esistono tre tipi fondamentali di macchine a stati finite: automi di Mealy, automi di Moore e
automi a stati finiti non deterministici. Gli automi di Mealy hanno un'uscita che dipende sia
dallo stato corrente sia dall'ingresso ricevuto, mentre gli automi di Moore hanno un'uscita
che dipende solo dallo stato corrente. Entrambi sono considerati automi deterministici e
sono equivalenti, in quanto è possibile trasformare un automa di Mealy in uno di Moore
che produce lo stesso risultato terminale.
Un diagramma di stato è definito dalla sestupla, che include un insieme finito e non vuoto
di stati, un insieme di ingressi, un insieme di uscite, una funzione di trasformazione per
passare da uno stato all'altro, una funzione di uscita e uno stato iniziale.
Questi diagrammi possono essere rappresentati graficamente tramite un grafo orientato,
noto come diagramma degli stati, o in forma tabellare. Gli stati sono rappresentati come
rettangoli smussati e le transizioni come frecce. È possibile etichettare le transizioni con
eventi, azioni e condizioni di guardia per specificare i comportamenti delle transizioni.
Gli eventi possono essere di diversi tipi, come eventi di chiamata, eventi di segnale, eventi
di variazione e eventi temporali. Le azioni possono essere istantanee o richiedere un
intervallo di tempo finito e possono essere associate a transizioni interne o agli eventi
speciali di ingresso e uscita.
Questi diagrammi sono strumenti potenti per la modellazione del comportamento dinamico
di un sistema e aiutano a specificare in modo preciso le regole di utilizzo dell'oggetto,
fornendo anche una base per la generazione dei casi di test e per la validazione del
comportamento del sistema.
11
I diagrammi di attività sono strumenti di modellazione UML utilizzati per descrivere il flusso
di lavoro e i processi, in particolare i processi di business. Consistono in un insieme di
attività connesse da transizioni che definiscono il passaggio da una fase all'altra del
processo.
Nelle attività, i nodi azione rappresentano unità di lavoro atomiche, istantanee e non
interrompibili. Altri nodi, come decisioni, fusioni, biforcazioni e ricongiunzioni, consentono
di modellare il flusso del processo. Le decisioni permettono di specificare precorsi
alternativi basati su condizioni di guardia, mentre le biforcazioni consentono di modellare
flussi concorrenti o attività parallele.
Il diagramma di attività può anche includere nodi oggetti, che rappresentano istanze delle
entità coinvolte nel processo. I flussi di oggetti descrivono il passaggio di oggetti tra le
attività, consentendo di specificare l'output di un'attività e il suo impatto sullo stato degli
oggetti coinvolti. In questo modo, è possibile mantenere coerenza con il diagramma degli
stati, descrivendo le azioni che modificano lo stato di un oggetto.
12
Il diagramma di deployment, o di allocazione, è uno strumento di modellazione UML
utilizzato per descrivere come i componenti software (sottosistemi, driver, etc.) vengono
allocati sulle unità fisiche di elaborazione, sia hardware che software. Non è necessario
modellare ogni dettaglio del sistema, ma solo gli elementi architetturalmente significativi.
13
La progettazione è una fase chiave nello sviluppo del software, poiché si tratta di una
transizione dalla specifica dei requisiti all'implementazione effettiva del sistema. L'obiettivo
principale della progettazione è definire l'architettura del sistema software, cercando di
risolvere il problema specificato durante l'analisi dei requisiti.
14
I diagrammi dei package sono strumenti importanti nella fase di progettazione del
software. Essi permettono di organizzare logicamente gli elementi del sistema, inclusi altri
package e diagrammi, all'interno di uno spazio di nomi incapsulato. Un package viene
rappresentato graficamente come una cartella.
Un package può contenere altri package, creando una gerarchia di package annidati. I
package annidati vedono tutti gli elementi pubblici del package ospite, ma il viceversa non
è vero, a meno che non vi sia una relazione di accesso tra i package.
• Dipendenza d'uso: indica che il cliente utilizza alcuni servizi forniti dal package.
• Dipendenza di astrazione: modella dipendenze tra elementi del modello UML a
diversi livelli di astrazione, come tra analisi e progettazione.
• Dipendenza di permesso: concerne la capacità di un elemento del modello di
accedere a un altro elemento, come le dipendenze di import e access.
Le relazioni di import e access tra package possono influenzare l'analisi di impatto per
eventuali manutenzioni, poiché un cambiamento in un package importato può avere
ripercussioni su altri package. La dipendenza di import è transitiva, mentre la dipendenza
di access non lo è. Questi aspetti sono importanti per comprendere l'impatto dei
cambiamenti durante la fase di manutenzione del software.
15
La progettazione di alto livello si basa sulla progettazione dei componenti e delle loro
interconnessioni. Per garantire una progettazione accurata, si definiscono le interfacce,
che rappresentano un insieme di funzionalità denominate da un nome. Un'interfaccia è
simile a una classe, ma non contiene attributi, solo operazioni. L'obiettivo è separare le
specifiche delle funzionalità dalla loro implementazione. Le interfacce servono come
contratti tra chi le implementa e chi le utilizza.
Per rappresentare le interfacce in UML, si utilizzano due notazioni: il simbolo della classe
con lo stereotipo <<interface>> o la notazione a palloncino (lollipop). Le interfacce sono
collegate alle classi che le implementano attraverso una relazione di realizzazione.
Nella progettazione per componenti, si definiscono i moduli del sistema e le interfacce che
offrono e richiedono servizi. Queste interfacce vengono accoppiate attraverso connettori di
assemblaggio. Le interfacce possono essere raggruppate in porte, che rappresentano
insiemi semanticamente coesi di interfacce.
UML prevede anche i sottosistemi, che sono parti di un sistema che a loro volta formano
un sistema più piccolo. Un sottosistema è un'unità di scomposizione per un sistema più
grande e può essere istanziato in virtù delle parti che lo compongono.
16
I pattern architetturali sono soluzioni predefinite per problemi ricorrenti nello sviluppo del
software, proposti da Christopher Alexander. Esistono diversi tipi di pattern, e tra questi, i
pattern architetturali e i design pattern.
• Pattern Architetturali:
• I pattern architetturali sono ampi schemi strutturali che definiscono l'organizzazione
e la distribuzione del software. Essi facilitano il riuso e la comprensione delle
componenti, fornendo un vocabolario comune. Tra i principali pattern architetturali vi
sono:
• Model-View-Controller (MVC): Un archetipo ampiamente utilizzato, in particolare
nelle tecnologie Java. Divide l'applicazione in tre componenti: Model
(rappresentazione dei dati), View (presentazione) e Controller (logica di gestione
degli eventi). MVC consente di separare i dati dalla presentazione, migliorando la
manutenibilità e la flessibilità dell'applicazione.
• Boundary-Control-Entity (BCE): Utilizzato in contesti object-oriented, separa
l'applicazione in tre gruppi di classi o package: Boundary (interfacciamento con
l'utente), Control (logica di business) e Entity (rappresentazione dei dati). Questo
pattern aiuta a assegnare responsabilità diverse agli oggetti e facilita la gestione dei
dati.
• Broker: Implementa un meccanismo di comunicazione publish/subscribe tra client e
server, disaccoppiando i due e consentendo un'interazione indiretta tramite un
intermediario (il broker).
• Pipe & Filter: Organizza le applicazioni come una catena di processi, in cui i dati
fluiscono attraverso filtri consecutivi, ognuno responsabile di un'operazione
specifica. Questo pattern promuove la modularità e la riusabilità delle componenti.
• Client/Server: Definisce un modello di interazione tra client e server, in cui il server
offre servizi passivamente e il client richiede attivamente tali servizi. Questo pattern
è comunemente utilizzato in applicazioni distribuite.
• Design Pattern:
• I design pattern sono soluzioni specifiche per problemi di progettazione di dettaglio
e sono spesso implementati utilizzando il paradigma di programmazione orientato
agli oggetti. Tra i design pattern più noti vi sono:
• Singleton: Garantisce che una classe abbia una sola istanza e fornisce un punto di
accesso globale a tale istanza.
• Factory Method: Definisce un'interfaccia per la creazione di oggetti, consentendo
alle sottoclassi di decidere quale classe istanziare.
• Observer: Consente a un oggetto (observable) di notificare automaticamente i suoi
osservatori quando cambia il suo stato.
• Strategy: Consente di definire una famiglia di algoritmi e renderli intercambiabili
durante l'esecuzione.
• Decorator: Aggiunge dinamicamente funzionalità aggiuntive a un oggetto senza
modificare la sua struttura di base.
Questi pattern aiutano a scrivere codice pulito, mantenibile e flessibile, consentendo la
risoluzione efficiente di problemi comuni durante lo sviluppo del software.
17
Il testo presenta cinque pattern di design: Singleton, Façade, Adapter, Composite e
Observer.
• Singleton:
• Il pattern Singleton è un pattern creazionale che assicura l'esistenza di una sola
istanza di una classe. Viene utilizzato quando è necessario avere un unico oggetto
di una determinata classe nel sistema. Il pattern impedisce la creazione di istanze
aggiuntive della classe e fornisce un metodo statico per accedere all'unica istanza
esistente.
• Façade:
• Il pattern Façade è un pattern strutturale che semplifica l'interazione con un
sottosistema complesso fornendo un'interfaccia unificata e di alto livello. Questo
permette al client di accedere a funzionalità più astratte senza dover conoscere i
dettagli interni del sottosistema. Il Façade nasconde la complessità del sistema,
promuovendo un basso accoppiamento tra client e sottosistema.
• Adapter:
• Il pattern Adapter è un altro pattern strutturale utilizzato per convertire l'interfaccia di
una classe esistente in un'interfaccia desiderata dal client. Viene impiegato quando
si desidera utilizzare una classe esistente che non corrisponde direttamente alle
esigenze del client. L'Adapter agisce come un ponte tra il client e l'oggetto adattato,
consentendo loro di collaborare in modo compatibile.
• Composite:
• Il pattern Composite è un pattern strutturale che permette di trattare un insieme di
oggetti simili come un'unica entità. Un oggetto Composite può essere composto da
altri oggetti, chiamati Componenti, che possono essere sia foglie (Leaf) che altri
oggetti composti. Il pattern consente di manipolare gli oggetti composti in modo
uniforme come gli oggetti individuali, facilitando la gestione di strutture ad albero
complesse.
• Observer:
• Il pattern Observer è un pattern comportamentale che stabilisce una dipendenza
"uno a molti" tra oggetti. Quando un oggetto, chiamato Subject, cambia stato,
notifica automaticamente tutti gli oggetti dipendenti, chiamati Observer,
consentendo loro di essere aggiornati con il nuovo stato. Il pattern è utile per
implementare il modello publish/subscribe e permette una comunicazione asincrona
tra i soggetti e gli osservatori.
Ogni pattern di design è pensato per risolvere specifici problemi di progettazione del
software e promuovere principi come la riusabilità del codice, la separazione delle
responsabilità e la facilità di manutenzione. L'utilizzo appropriato di questi pattern può
migliorare la struttura, la flessibilità e l'efficienza di un sistema software.
17
Il processo di verifica e validazione del software mira a garantire che il software soddisfi le
esigenze dell'utente e rispetti i requisiti specificati durante la fase di analisi e specifica dei
requisiti. La validazione risponde alla domanda "stiamo costruendo il software giusto?"
mentre la verifica risponde a "stiamo costruendo correttamente il software?"
Un software è corretto se rispetta i requisiti specificati, ma ciò non garantisce che soddisfi
le reali esigenze dell'utente e superi la validazione. Il processo di testing è un'attività di
esecuzione del software con lo scopo di scoprire eventuali malfunzionamenti o difetti.
Esso si basa sulla catena "fault-error-failure", dove un difetto nel codice, se attivato,
diventa un errore, e se si manifesta, provoca un malfunzionamento.
I criteri di adeguatezza sono predicati che valutano la copertura dei test. Un test è buono
se individua nuovi malfunzionamenti e, di conseguenza, i test case devono essere definiti
con questo obiettivo.
Esistono diverse tecniche di testing, tra cui black-box e white-box. Nel black-box, i casi di
test sono definiti solo dalle specifiche funzionali, mentre nel white-box, sono definiti sulla
base della conoscenza del codice sorgente.
18
Il test strutturale nel contesto del software è un processo di verifica e validazione che si
concentra sull'analisi delle strutture interne del programma per identificare potenziali errori
e problemi. Esso comprende due approcci principali: il black-box testing e il white-box
testing.
Per gestire la vastità dei casi di test possibili, si utilizzano modelli come il Control Flow
Graph (CFG) e il Call Graph (CG) per identificare le parti più complesse del codice e
focalizzare i test su quelle aree critiche.
Per selezionare i casi di test, si utilizzano diversi criteri di copertura, tra cui:
• Criterio delle istruzioni: Si assicura che ogni istruzione del codice sia eseguita
almeno una volta durante i test.
• Criterio delle decisioni: Si verifica che tutte le possibili decisioni, come le condizioni
"if" e "switch", siano esaminate durante i test.
• Criterio modificato MC/DC: Questo criterio si concentra su decisioni composte,
cercando di testare solo le condizioni rilevanti e ridurre così il numero di casi di test
necessari.
Infine, il test di robustezza, che coinvolge l'uso di dati scorretti o inattesi come input, aiuta
a verificare come il software reagisce in situazioni impreviste.
19
Il test funzionale è una tecnica di verifica e validazione del software che si basa sulle
specifiche funzionali, ovvero le descrizioni informali di ciò che il software dovrebbe fare.
Questo tipo di test è solitamente implementato come black-box testing o specification-
based testing, il che significa che non richiede la conoscenza interna del codice sorgente.
Un vantaggio del test funzionale è che può essere condotto in modo economico, poiché
non richiede la conoscenza interna del codice. Inoltre, può essere applicato a diversi livelli
di test, dal test di unità al test di sistema, rendendolo un'ottima tecnica di raffinamento
delle specifiche.
Il processo di test funzionale inizia con l'analista che specifica i requisiti funzionali e li
passa al team di progettazione dei casi di test. Questa fase può essere seguita subito
dopo la specifica dei requisiti o rimandata alla fase di codifica. È fondamentale che le
specifiche siano accurate e testabili per ottenere test suite efficaci. Talvolta, l'analista
potrebbe non considerare il testing durante la stesura delle specifiche, ma attraverso il
feedback del team di progettazione dei casi di test, le specifiche possono essere
migliorate.
Il category partition testing è una tecnica di test combinatoriale che permette di strutturare
le specifiche di test in un insieme di proprietà o attributi variabili in insiemi di valori. Questo
metodo aiuta a selezionare un sottoinsieme rappresentativo dei casi di test da eseguire,
riducendo il numero totale di test necessari.
Il pairwise combinatorial testing è un altro tipo di testing combinatoriale che considera tutte
le possibili combinazioni di input a coppie (o n-uple). Poiché spesso i maggiori casi di
fallimento sono causati da combinazioni di pochi input, questa tecnica riduce notevolmente
il numero di casi di test necessari, testando ogni coppia di input solo una volta.
Infine, i vincoli (error, single, property) possono essere introdotti per eliminare
combinazioni invalide o ridondanti di valori delle categorie, ottimizzando ulteriormente il
numero di casi di test.
Complessivamente, il test funzionale è uno strumento potente per individuare difetti nel
software e per garantire che le specifiche funzionali siano soddisfatte correttamente,
contribuendo a migliorare la qualità del prodotto software.
20
Il test model-based è un metodo di derivazione dei casi di test che si basa sulla verifica del
comportamento previsto del programma tramite un modello formale. In questo approccio, il
software viene eseguito non rispetto alle specifiche, ma in base alle predizioni fatte
utilizzando il modello. Un esempio di modello può essere una macchina a stati che
rappresenta il comportamento di un'applicazione, come un orologio che visualizza l'ora in
formato digitale o analogico.
Esistono diversi criteri per selezionare i casi di test da eseguire usando il modello. Un
criterio può richiedere di testare tutti gli stati del modello, un altro può prevedere di
eseguire tutte le transizioni. Altri criteri possono essere basati su random walk, ovvero
percorrere in modo casuale il diagramma di stato, simile all'approccio del random testing.
Tuttavia, è importante notare che i difetti nel software non sono sempre equiprobabili,
quindi l'efficacia di questo approccio può variare.
La generazione dei casi di test da un modello è relativamente semplice, il costo è ridotto e
può essere automatizzata facilmente. Un aspetto cruciale è garantire che il modello
rappresenti correttamente il comportamento del sistema reale. Per fare ciò, è necessario
eseguire un test di conformità, verificando che il modello catturi accuratamente il
comportamento del sistema. La validazione del modello può essere fatta confrontando il
suo comportamento con il comportamento del sistema reale in casi noti. Tuttavia, per i casi
non noti, dove non si conosce il comportamento del sistema reale, è più difficile
determinare se il modello sia adeguato o meno.
I diagrammi di stato sono uno strumento comune per rappresentare i modelli a stati e
possono essere utilizzati per generare automaticamente i casi di test, basandosi sul
comportamento descritto dalla macchina a stati.
In sintesi, il test model-based è un approccio che si basa sulla verifica del comportamento
del software tramite un modello formale, ma è importante assicurarsi che il modello sia
accurato rispetto al sistema reale per ottenere risultati validi.
21
I modelli di qualità del software sono strumenti che definiscono un insieme di attributi
esaustivi e privi di sovrapposizioni, relativi a un prodotto software, e le dipendenze logiche
tra di essi. Questi modelli sono gerarchici e possono essere differenziati a seconda che
interessino chi compra o chi produce il software, sebbene gli attributi di qualità del
processo di sviluppo influenzino anche quelli rilevanti per gli utilizzatori del software.
I modelli di qualità del software hanno diversi livelli. Il primo livello descrive le
caratteristiche di qualità del prodotto da diversi punti di vista, come le qualità esterne,
interne e in uso. Le qualità esterne riguardano il comportamento dinamico del software
nell'ambiente d'uso, mentre le qualità in uso esprimono l'efficacia ed efficienza del
software rispetto alle esigenze degli utenti. Le qualità interne, invece, sono attributi statici
del prodotto, indipendenti dall'esecuzione.
La norma ISO/IEC 9126-1 definisce sei caratteristiche principali di qualità del software,
ognuna con diverse sottocaratteristiche misurabili tramite metriche specificate nelle parti 2,
3 e 4 dello standard.
Esistono anche altre norme come ISO/IEC 9241, che definisce le caratteristiche
dell'usabilità, e ISO 12119, che riguarda le caratteristiche di qualità dei software
"Commercial off-the-shelf" (COTS).
Le metriche del software sono strumenti utilizzati per misurare quantitativamente le
caratteristiche del prodotto o del processo. Esistono diverse metriche dimensionali e di
complessità che possono essere applicate per misurare il software, ad esempio il numero
di linee di codice (LOC), il numero ciclomatico e il flusso di informazione.
Nel contesto dell'ingegneria del software, le metriche sono utilizzate per valutare la
produttività, la qualità e la complessità del software. Possono essere utilizzate sia a
posteriori per misurare le caratteristiche del prodotto software, sia a priori per stimare
costi, tempi o risorse necessarie per lo sviluppo.
22
La stima dei costi è una fase cruciale nella pianificazione di un progetto poiché permette di
determinare il prezzo del prodotto finale, dimensionare il team di sviluppo e gestire il
processo di produzione. Tale stima è altresì applicabile ai prodotti software già esistenti,
per valutare se una manutenzione (correttiva, adattativa o perfettiva) risulta più
conveniente rispetto alla realizzazione ex-novo.
Tra i principali metodi di stima dei costi, basati su principi empirici, spicca l'analisi dei punti
di funzione (function points). Questa metrica misura la quantità di funzionalità del software,
e i punti funzione corrispondono al valore delle funzionalità prodotte. Calcolando i punti
funzione, si può stimare la quantità di sforzo richiesta per sviluppare il software,
indipendentemente dalla tecnologia impiegata.
Il metodo degli "unadjusted function points" (UFPM) è standardizzato dall'ISO e dal gruppo
IFPUG (International Function Point User Group) e si suddivide in due stati:
In conclusione, l'analisi dei punti di funzione offre un metodo vantaggioso e applicabile fin
dalla fase iniziale del ciclo di vita del progetto, permettendo una stima dei costi basata sui
requisiti funzionali del software. Tale processo di stima è fondamentale per una
pianificazione accurata e una gestione efficace del progetto software.
23
Il ciclo di vita del software è il processo seguito per costruire, consegnare ai clienti e far
evolvere il prodotto software, dalla sua ideazione fino al ritiro quando diventa obsoleto.
Esistono diversi modelli che cercano di catturare l'essenza di questo processo, ma tutti
condividono l'obiettivo di stabilire l'ordine delle attività e i criteri di transizione tra le diverse
fasi.
Uno dei primi e più primitivi modelli è il "code and fix", tipico di programmatori singoli, in cui
il codice viene scritto e corretto a mano mano che vengono identificati errori o richieste di
nuove funzionalità. Tuttavia, questo modello è inadeguato per lo sviluppo moderno del
software a causa della mancanza di una gestione pianificata della complessità, la difficoltà
nel gestire il personale e il processo non prevedibile con qualità non misurabile.
Per superare le limitazioni del modello a cascata, sono state sviluppate varie alternative,
come il modello a cascata con retroazione (feedback), che introduce un feedback in ogni
fase per rilevare errori prima del rilascio, ma non anticipa i possibili cambiamenti. I modelli
evolutivi, come il modello a rilasci incrementali e il modello trasformazionale, permettono
una maggiore flessibilità e adattabilità rispetto ai cambiamenti dei requisiti.
Infine, le metodologie agili, come Extreme Programming (XP) e Scrum, sono modelli di
sviluppo leggeri e flessibili che si concentrano sulla soddisfazione del cliente, adattandosi
ai cambiamenti e incoraggiando la collaborazione tra team e committenti. Il modello
DevOps è un'evoluzione delle metodologie agili, focalizzato sull'integrazione tra sviluppo e
operazioni per migliorare l'efficienza e la sicurezza nella messa in esercizio del software.