Verifica e validazione: analisi dinamica

Definizione e problemi

L'analisi dinamica non è altro che l'esecuzione di test, cioè prove su del codice in esecuzione. Purtroppo nello sviluppo di un software il codice nasce molto tardi; per questo, il testing dev'essere rapido (efficiente) ed efficace[1]. Un altro problema dei test è la loro non esaustività: è possibile eseguirli solo su un insieme finito di casi, che non sono quasi mai tutti i casi possibili.

La pianificazione del testing deve quindi avvenire il prima possibile. Il primo momento utile è la progettazione del sistema.

Il debugging non è verifica: esso nasce da un errore che si è manifestato inaspettatamente, mentre la verifica viene pianificata a monte dello sviluppo.

Terminologia

Compito del test è trovare errori nel codice. Distinguiamo tre livelli di errore:

Quindi: un guasto cause un errore, il quale può produrre un malfunzionamento. (Nota: il glossario IEEE non concorda pienamente con le definizioni sopra.)

Organizziamo il testing nelle seguenti classi:

Compromesso

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 testing vale infatti la "legge del rendimento decrescente[2]": 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).

Criteri guida per i test

Oggetto di una prova può essere:

L'obiettivo di ogni prova dev'essere specificato in modo chiaro per ogni caso di prova (test case): un test è buono se è ripetibile. Per essere ripetibile, ogni test deve stare attento 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.

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: Any failed execution must yield a test case, to be permanently included in the project's test suite (Bertrand Meyer).

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 testing.

Test di unità

I test di unità sono naturalmente più numerosi dei test di integrazione e di sistema: circa due terzi dei difetti rilevati tramite analisi dinamica sono dovuti ai test di unità.

Un concetto fondamentale nei test di unità è quello di copertura (coverage). 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.

Alcuni criteri notevoli di copertura sono i seguenti:

Di queste, le più importanti sono lo statement coverage e, ancor più, il branch coverage. È 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 branch coverage è il MC/DC (Modified Condition/Decision Coverage).

Distinguiamo due categorie di test di unità:

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.

Test di integrazione

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.

Basandoci sul fatto che i sistemi software sono (al giorno d'oggi) sistemi gerarchici, possiamo individuare due principali strategie d'integrazione:

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.

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.

Test di sistema

I test di sistema verificano il comportamento del sistema rispetto ai suoi requisiti. Essi sono inerentemente funzionali: non hanno bisogno di conoscere la logica interna del sistema.

  1. È interessante notare che il testing 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.
  2. In inglese diminishing returns.
  3. Qui stiamo studiando un programma come un albero.
  4. La copertura interessa soltanto i test strutturali, non quelli funzionali.