forked from ggiuffre/sweki
-
Notifications
You must be signed in to change notification settings - Fork 3
/
14_andinamica.html
106 lines (93 loc) · 10.7 KB
/
14_andinamica.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="it" lang="it">
<head>
<title>Verifica e validazione: analisi dinamica - sweki</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="author" content="FIUP" />
<meta name="keywords" content="sweki, ingegneria, software, verifica, validazione, analisi, dinamica" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="main.css" />
<link rel="prev" href="13_anstatica.html" />
<link rel="next" href="15_quantif.html" />
</head>
<body>
<div id="header">
<h1>
<a class="prev" title="prev" href="13_anstatica.html"><</a>
<a href="index.html"><acronym title="SoftWare Engineering wiKI">sweki</acronym></a>
<a class="next" title="next" href="15_quantif.html">></a>
</h1>
<h2>la wiki di Ingegneria del Software</h2>
</div>
<div id="content">
<h1>Verifica e validazione: analisi dinamica</h1>
<h2>Definizione e problemi</h2>
<p>L'analisi dinamica non è altro che l'esecuzione di <strong><em>test</em></strong>, cioè prove su del codice in esecuzione. Purtroppo nello sviluppo di un software il codice nasce molto tardi; per questo, il <em xml:lang="en">testing</em> dev'essere rapido (efficiente) ed efficace<sup class="footnote"><a id="txt1" href="#ftn1">[1]</a></sup>. Un altro problema dei test è la loro <strong>non esaustività</strong>: è possibile eseguirli solo su un insieme finito di casi, che non sono quasi mai tutti i casi possibili.</p>
<p>La pianificazione del <em xml:lang="en">testing</em> deve quindi avvenire il prima possibile. Il primo momento utile è la progettazione del sistema.</p>
<p>Il debugging <em>non</em> è verifica: esso nasce da un errore che si è manifestato inaspettatamente, mentre la verifica viene pianificata a monte dello sviluppo.</p>
<h2>Terminologia</h2>
<p>Compito del test è trovare errori nel codice. Distinguiamo tre livelli di errore:</p>
<ul>
<li><strong>Malfunzionamento</strong> (<em xml:lang="en">failure</em>): l'esecuzione è difforme dalle attese. I malfunzionamenti riguardano il comportamento del software.</li>
<li><strong>Errore</strong> (<em xml:lang="en">error</em>): stato del sistema che, se attivato, produce un malfunzionamento; se l'errore non viene attivato, rimane nascosto ma esiste. Gli errori sono quindi stati del sistema.</li>
<li><strong>Guasto</strong> o difetto (<em xml:lang="en">fault</em>): causa dell'errore; può essere un guasto del computer o del software. I guasti possono essere malfunzionamenti di un sotto-sistema: i sistemi software sono gerarchici (annidati).</li>
</ul>
<p>Quindi: un guasto cause un errore, il quale può produrre un malfunzionamento. (Nota: il glossario IEEE non concorda pienamente con le definizioni sopra.)</p>
<p>Organizziamo il <em xml:lang="en">testing</em> nelle seguenti classi:</p>
<ul>
<li><strong>caso di prova</strong> (<em xml:lang="en">test case</em>) — una tripla <ingresso, uscita, ambiente>;</li>
<li><strong>batteria di prove</strong> (<em xml:lang="en">test suite</em>) — un insieme di casi di prova;</li>
<li><strong>prova</strong> — una procedura di prova e una batteria di prove.</li>
</ul>
<h2>Compromesso</h2>
<p>Esiste un compromesso, come tra efficienza ed efficacia, tra il numero di test sufficienti a verificare il prodotto e lo sforzo allocato a progetto. Sul <em xml:lang="en">testing</em> vale infatti la "legge del <strong>rendimento decrescente</strong><sup class="footnote"><a id="txt2" href="#ftn2">[2]</a></sup>": man mano che aumento lo sforzo, il rendimento cresce inizialmente ma poi diminuisce sempre più. Questo avviene, ad esempio, quando un produttore aumenta la produzione e, a un certo punto, oltrepassa la domanda: i profitti iniziano ad essere negativi. Così, arriva un tempo in cui fare altri test non aggiunge nulla, cioè non trova errori (e la funzione primaria dei test è trovare errori).</p>
<h2>Criteri guida per i test</h2>
<p>Oggetto di una prova può essere:</p>
<ul>
<li>il sistema nel suo complesso (per i test di sistema);</li>
<li>parti del sistema in relazione funzionale, d'uso, di comportamento o di struttura (per i test di integrazione);</li>
<li>singole unità (per i test di unità).</li>
</ul>
<p>L'obiettivo di ogni prova dev'essere specificato in modo chiaro per ogni caso di prova (<em>test case</em>): un test è buono se è ripetibile. Per essere ripetibile, ogni test deve stare anche allo stato, all'ambiente. Il Piano di Qualifica specifica quali e quante sono le prove da effettuare. I test non sostituiscono la progettazione; piuttosto, sono speculari ad essa.</p>
<p>Un test deve cercare di far fallire il software: dev'essere "cinico"! I test che falliscono devono essere eseguiti sempre, dal momento in cui sono falliti e il software è stato corretto: <q>Any failed execution must yield a test case, to be permanently included in the project's test suite</q> (Bertrand Meyer).</p>
<p>All'origine, un test va specificato in fase di progettazione; dopo essere stato implementato ed eseguito, esso va tenuto in un archivio, come documentazione dell'attività di <em xml:lang="en">testing</em>.</p>
<h2>Test di unità</h2>
<p>I test di unità sono naturalmente più numerosi dei test di integrazione e di unità: circa due terzi dei difetti rilevati tramite analisi dinamica sono dovuti ai test di unità.</p>
<p>Un concetto fondamentale nei test di unità è quello di <strong>copertura</strong> (<em xml:lang="en">coverage</em>). Con questo termine si intende la percentuale di codice che un caso di prova è in grado di eseguire, cioè quanto codice sorgente è stato effettivamente attraversato durante il caso di prova; ad esempio, una copertura del 100% indica che l'esecuzione di un test ha coperto tutti i casi possibili del codice in esame.</p>
<p>Alcuni criteri notevoli di copertura sono i seguenti:</p>
<ul>
<li><em xml:lang="en">function coverage</em> — quante funzioni (sottoprocedure) sono state eseguite?</li>
<li><em xml:lang="en">statement coverage</em> — quante istruzioni (righe di codice, all'incirca) sono state eseguite?</li>
<li><em xml:lang="en">branch coverage</em> — quanti rami<sup class="footnote"><a id="txt3" href="#ftn3">[3]</a></sup> del programma sono stati eseguiti?</li>
<li><em xml:lang="en">condition coverage</em> — quante espressioni logiche hanno assunto entrambi i valori possibili?</li>
</ul>
<p>Di queste, le più importanti sono lo <em xml:lang="en">statement coverage</em> e, ancor più, il <em xml:lang="en">branch coverage</em>. È sempre bene che i test di unità coprano il codice al 100% rispetto a questi ultimi due criteri; tuttavia, va ricordato che la copertura totale del codice non assicura l'assenza di difetti! Un criterio ancora più forte del <em xml:lang="en">branch coverage</em> è il <abbr title="Modified Condition/Decision Coverage">MC/DC</abbr> (<em xml:lang="en">Modified Condition/Decision Coverage</em>).</p>
<p>Distinguiamo due categorie di test di unità:</p>
<ul>
<li>Un <strong>test funzionale</strong> è un test a scatola chiusa (<em xml:lang="en">black box</em>). Fa riferimento alla specifica di un'unità e osserva il suo comportamento dal di fuori; dati in ingresso che producono un medesimo comportamento funzionale costituiscono una classe di equivalenza e sono un caso di prova.</li>
<li>Un <strong>test strutturale</strong> è un test a scatola aperta (<em xml:lang="en">white box</em>): verifica la logica interna del codice dell'unità. Persegue la massima copertura<sup class="footnote"><a id="txt4" href="#ftn4">[4]</a></sup> del codice sorgente; dati in ingresso che attivano un medesimo percorso costituiscono un caso di prova.</li>
</ul>
<p>Dobbiamo eseguire i test funzionali prima di quelli strutturali, se non vogliamo rischiare di analizzare una struttura che non svolge il compito giusto. I test funzionali vanno sempre integrati con test strutturali.</p>
<h2>Test di integrazione</h2>
<p>I test d'integrazione fanno parte di un processo più ampio che è quello dell'integrazione delle parti del sistema. Le parti vanno integrate secondo una strategia. Ad esempio è sempre bene assemblare le parti in modi incrementale (quindi reversibile), seguendo le dipendenze nell'architettura: aggiungendo una parte nuova ad un insieme ben verificato, i difetti rilevati in un test d'integrazione saranno probabilmente dovuti alla parte nuova, facilitando così la ricerca di quale parte sia da correggere.</p>
<p>Basandoci sul fatto che i sistemi software sono (al giorno d'oggi) sistemi gerarchici, possiamo individuare due principali <strong>strategie d'integrazione</strong>:</p>
<ul>
<li>Dal basso (<span xml:lang="en">bottom-up</span>): si sviluppano e si integrano prima le parti con minore dipendenza funzionale (<em xml:lang="en">fan-out</em>) e maggiore utilità (<em xml:lang="en">fan-in</em>). Così facendo, si "risale" l'albero delle dipendenze. Si economizzano molti stub ma le funzionalità di alto livello compaiono più tardi.</li>
<li>Dall'alto (<span xml:lang="en">top-down</span>): si sviluppano prima le parti più esterne (di alto livello, con molte dipendenze) e poi si scende nell'albero delle dipendenze. Qui le funzionalità di alto livello vengono verificate sin da subito e si può mostrare al committente una bozza del sistema.</li>
</ul>
<p>I test d'integrazione si applicano alle componenti specificate in progettazione architetturale; perciò, questi test rilevano difetti di progettazione. L'integrazione delle componenti costituisce il sistema completo.</p>
<p>Quanti test d'integrazione è bene fare? tanti quante sono le interfacce nell'architettura del sistema: i test d'integrazione devono accertare che i dati scambiati attraverso ciascuna interfaccia siano conformi alla propria specifica.</p>
<h2>Test di sistema</h2>
<p>I test di sistema verificano il comportamento del sistema rispetto ai suoi <strong>requisiti</strong>. Essi sono inerentemente funzionali: non hanno bisogno di conoscere la logica interna del sistema.</p>
</div>
<div id="footnotes">
<ol>
<li id="ftn1">È interessante notare che il <em xml:lang="en">testing</em> viene fatto sempre: se non lo fa il fornitore, lo farà l'utente. Ovviemante, dare all'utente un software non testato è una cosa che non va fatta.</li>
<li id="ftn2">In inglese <em>diminishing returns</em>.</li>
<li id="ftn3">Qui stiamo studiando un programma come un albero.</li>
<li id="ftn4">La copertura interessa soltanto i test strutturali, non quelli funzionali.</li>
</ol>
</div>
</body>
</html>