Progettazione dei test funzionali e non funzionali (black-box e white-box)

Progettazione dei test funzionali e non funzionali (black-box e white-box)

I metodi di test

Il controllo dinamico, o test, è una delle tecniche più note e più usate nella valutazione della qualità dei prodotti software. L’intuitività dell’approccio non significa che possa essere eseguito in modo primitivo. Come osservato da Dijkstra, il test può rilevare la presenza di malfunzionamenti in un programma, ma non dimostrarne l’assenza. I metodi di progettazione del test che tratteremo in questo capitolo definiscono tecniche e criteri per ottenere buone approssimazioni del test ideale che potrebbe garantre la correttezza del programma. Esamineremo inoltre le strategie da adottare nella conduzione dei test e le tecniche per produrre i dati corretti da usare come pietra di paragone per valutare la correttezza dei risultati dei test.

L’importanza dei metodi per la progettazione e l’analisi dei risultati dei test deriva da questa considerazione: in un processo di produzione software è un obiettivo primario ottenere il miglior rapporto possibile fra la qualità del prodotto e il tempo e le risorse impiegate nelle attività di controllo.

Progettazione dei test

La progettazione dei test è l’attività di definizione di un insieme di casi di test. Si definisce come caso di prova (o caso di test, o test case) una tripla

<input, output, ambiente>

che specifica i dati di input, i risultati attesi, ed una descrizione dell’ambiente di esecuzione. Un insieme (o una sequenza) di casi di test è detta batteria di test (o test suite) o test book.

Non esistono test che siano validi in generale: ogni test deve essere costruito su misura (ad hoc), in dipendenza delle caratteristiche del software da controllare e dei fattori di qualità che si vogliono valutare. Questa realtà, insieme alle considerazioni economiche che sempre accompagnano ogni processo di sviluppo, è condensabile nelle tre seguenti affermazioni:

  1. la probabilità di rilevare il maggior numero di malfunzionamenti aumenta proporzionalmente al numero dei test eseguiti;
  2. i controlli di qualità, ivi compresi i test, hanno un costo che, ovviamente, è necessario tenere basso;
  3. i test non devono essere condotti artigianalmente: si perde la possibilità di valutare oggettivamente il valore dei controlli.

La progettazione dei test cerca di rispondere contemporaneamente a queste esigenze. I criteri che guidano la progettazione dei test possono essere divisi in due classi principali: criteri funzionali e criteri strutturali. I criteri descritti nei paragrafi seguenti sono formulati in generale, parleremo perciò di programmi più che di moduli o di sistemi software. Tuttavia, come vedremo, le caratteristiche proprie delle due classi rendono conveniente scegliere i criteri da applicare in dipendenza della fase del processo di sviluppo in cui il prodotto è sottoposto a controllo. Alle due classi principali di criteri si aggiungono strategie di test più articolate, ottenute combinando gli approcci funzionali e strutturali a partire da principî originali. Infine, come ultimo ma non meno importante aspetto della progettazione dei test, è necessario definire la pietra di paragone, l’oracolo, che genera dei valori (risultati attesi) con cui confrontare i risultati dei test, per evidenziare gli eventuali malfunzionamenti esibiti dal software.

Progettazione dei test funzionali e non funzionali (black-box e white-box)

Criteri funzionali o black-box

Sotto la dizione funzionale si raccolgono i criteri di progettazione dei test che si basano solamente sui requisiti di un programma. Nella terminologia inglese, spesso più frequente nel linguaggio tecnico, sono definiti come criteri black-box, indicando appunto che il programma è trattato come una scatola chiusa (letteralmente “nera”): ai fini dei test non è necessaria la conoscenza della sua realizzazione. Nella progettazione dei test interverranno solamente le caratteristiche esterne del programma: la sua interfaccia di ingresso/uscita e l’ambiente di esecuzione.
I criteri funzionali prevedono di selezionare l’insieme dei dati di ingresso che costituirà il test a partire dallo studio delle funzionalità di un programma. I vari criteri si distinguono in base alle regole con cui sono individuati i casi rilevanti che costituiranno la materia del test. Nel caso di specifiche espresse in linguaggio formale i criteri funzionali sono più facilmente applicabili e spesso è possibile ricorrere a strumenti automatici per generare direttamente i casi di test o, comunque, è possibile definire delle regole che rendono il procedimento automatizzabile. Quando invece le specifiche non sono espresse formalmente o peggio si hanno a disposizione solo i requisiti espressi in linguaggio naturale, i criteri funzionali risultano di più difficile applicazione e diventa fondamentale l’esperienza dei professionisti incaricati di progettare i test.

Nella migliore delle ipotesi, dalle specifiche si può ricavare una definizione esatta dell’insieme di tutti gli input di un programma: un test che eserciti il programma su questo insieme, cioè su tutti i possibili valori d’ingresso, è certamente esaustivo. Purtroppo nella pratica quest’evenienza è rara: nella maggior parte dei casi le specifiche non arrivano a questo livello di dettaglio. E quando anche si riesce ad avere una definizione dell’insieme dei possibili ingressi, normalmente risulta così grande che non è plausibile tentare di usarlo effettivamente come insieme di casi di test. Nel seguito presentiamo alcuni fra i più noti criteri funzionali descritti in letteratura: tutti hanno come comune obiettivo l’individuazione di insiemi di input di dimensioni limitate e comunque significativi per il test.

  1. Statistico. I casi di test sono selezionati in base alla distribuzione di probabilità dei dati di ingresso del programma. Il test è quindi progettato per esercitare il programma sui valori di ingresso più probabili per il suo utilizzo a regime, tali valori costituiscono il profilo operazionale del programma. Il vantaggio principale di questo criterio è che, nota la distribuzione di probabilità, la generazione dei dati di test è facilmente automatizzabile. Non sempre però è possibile ricavare dalle specifiche una distribuzione di probabilità e non sempre è detto che quella dedotta dalle specifiche corrisponda alle effettive condizioni d’utilizzo del software. In quest’ultima evenienza la natura prettamente utilitaristica di questo test potrebbe rivelarsi pericolosa. Un altro lato debole del test statistico è che la generazione casuale dei dati d’ingresso può scegliere valori per i quali può essere particolarmente oneroso calcolare il risultato atteso.
  2. Partizione dei dati d’ingresso. Il dominio dei dati di ingresso è ripartito in classi di equivalenza: due valori d’ingresso appartengono alla stessa classe di equivalenza se, in base ai requisiti, dovrebbero produrre lo stesso comportamento del programma. La progettazione dei casi di test prosegue in modo da esercitare il programma su valori rappresentanti di ognuna delle classi di equivalenza. Il criterio è economicamente valido solo per quei programmi per cui il numero dei possibili comportamenti è sensibilmente inferiore alle possibili configurazioni d’ingresso. D’altra parte, per come sono costruite le classi di equivalenza, i risultati attesi dal test sono noti e quindi non si pone il problema dell’oracolo. È infine necessario tener presente che il criterio è basato su un’affermazione generalmente plausibile, ma non vera in assoluto. Infatti, la deduzione che il corretto funzionamento sul valore rappresentante implichi la correttezza su tutta la classe di equivalenza dipende dalla realizzazione del programma e non è verificabile sulla base delle sole specifiche funzionali.
  3. Valori di frontiera. Anche questo criterio è basato su una partizione dei dati di ingresso. Le classi di equivalenza possono essere realizzate o in base all’eguaglianza del comportamento indotto sul programma o in base a considerazioni inerenti il tipo dei valori d’ingresso. Come dati di test sono considerati i valori estremi di ogni classe di equivalenza. In dipendenza di come sono definite le classi in cui l’insieme dei dati d’ingresso è ripartito, è possibile che non siano noti i risultati del programma in corrispondenza dei valori scelti, ciò significa che deve essere considerato il problema dell’oracolo. Questo criterio richiama i controlli sui valori limite tradizionali in altre discipline ingegneristiche per cui è vera la proprietà del comportamento continuo.
    In meccanica, ad esempio, una parte provata per un certo carico resiste con certezza a tutti i carichi inferiori. Questa proprietà non è applicabile al software: quando anche sia possibile stabilire una nozione di continuità nell’insieme dei dati d’ingresso, non è dato dedurre il comportamento continuo del programma. La giustificazione del criterio sta invece nella considerazione, diametralmente opposta, per cui nei programmi i valori limite sono frequentemente trattati in modo particolare. In questa prospettiva, il criterio dei valori di frontiera risulta un utile complemento ad altri criteri basati sulla partizione dei dati di ingresso.
  4. Grafo causa-effetto. L’applicazione di questo criterio si basa sulla costruzione, ottenuta a partire dai requisiti o dalle specifiche del programma, di un grafo che lega un insieme di fatti elementari di ingresso (cause) e di uscita (effetti) in una rete combinatoria che definisce delle relazioni di causa-effetto. I casi di test si ricavano risalendo il grafo a partire dalle combinazioni di fatti elementari d’uscita e ottenendo le combinazioni di fatti elementari di ingresso che le generano. Per il modo con cui i casi di test sono ricavati, il criterio risolve direttamente il problema dell’oracolo. Inoltre, il grafo causa-effetto può essere costruito durante la fase di validazione dei requisiti, per mettere in luce contraddizioni ed evidenziare parti mancanti di logica. In tal caso è anche possibile riusare il grafo durante la progettazione dei test.

Il principale vantaggio dei criteri black-box consiste nella possibilità di anticipare la progettazione dei test. I criteri non dipendono dal codice del programma da controllare: la progettazione dei test e la realizzazione dell’ambiente di test non sono perciò vincolate ad attendere la codifica del software.
Questa considerazione assume particolare importanza nel processo di sviluppo. La scelta dei criteri funzionali per la progettazione dei test di sistema, generalmente i più costosi da realizzare, permette di parallelizzare l’attività di realizzazione del prodotto e quella di progettazione e predisposizione dei test. Le informazioni contenute nel documento di analisi dei requisiti o, con maggior precisione formale, nelle specifiche, definiscono completamente le funzionalità del prodotto software e risultano quindi sufficienti per applicare i criteri funzionali.

Criteri strutturali o white box

Sotto la dizione strutturali si raccolgono i criteri di progettazione dei test che tengono conto della realizzazione del programma da controllare, che cioè si basano sulla struttura del codice. Idea di partenza e obiettivo dei test ispirati ai criteri strutturali è esercitare il programma in ogni sua parte. In letteratura e nel linguaggio tecnico comune, i criteri strutturali sono indicati con l’attributo inglese white-box, scelto per contrapposizione all’opacità evocata dalla dizione black-box usata per i criteri funzionali.

I criteri white-box sono basati sull’analisi del flusso di controllo o del flusso dei dati di un programma. La progettazione dei test fa riferimento a un grafo di flusso ricavato dall’analisi del codice del programma. Ad ogni nodo del grafo è associato un comando (statement); gli archi orientati rappresentano il legame di sequenzialità fra i comandi: a essi possono essere associate le condizioni che determinano le direzioni (branch) prese dal flusso del controllo in seguito all’esecuzione di comandi condizionali.

I criteri basati sull’analisi del flusso di controllo sono attualmente i più adottati. Storicamente, sono stati i primi a essere proposti in letteratura: alla maggior notorietà è seguita anche la realizzazione di strumenti automatici e la proposta di metodi che ne facilitano l’applicazione. I più noti test ispirati a criteri appartenenti a questa categoria sono:

  1. test sui comandi (statement test); ha l’obiettivo di esercitare i comandi del programma: in termini di grafo di flusso, ogni nodo deve essere percorso almeno una volta;
  2. test sulle decisioni (branch test); obiettivo del test è far assumere alle espressioni che controllano i comandi condizionali i valori di vero e falso almeno una volta; in termini di grafo di flusso sono esercitati gli archi che corrispondono alle decisioni; è importante notare che il test sulle decisioni include propriamente il test sui comandi, infatti in base alle proprietà topologiche dei grafi di flusso, escluso il caso banale di un programma senza branch, esercitare tutti i branch significa esercitare anche tutti gli archi del grafo (i test ispirati a questo criterio in letteratura si trovano citati come edge test), e quindi tutti i nodi;
  3. test sulle condizioni (condition test); è una variazione del branch test che invece di concentrarsi sul risultato del comando condizionale (il branch o la direzione percorsa) pone attenzione sul modo con cui la direzione è decisa, cioè sul modo con cui viene calcolata l’espressione booleana: ogni espressione booleana elementare deve assumere almeno una volta entrambi i valori di vero e falso; la differenza con il test precedente si nota in modo evidente quando le espressioni booleane sono costituite da molte condizioni composte logicamente;
  4. test sulle decisioni e sulle condizioni (decision/condition test); è possibile dimostrare che esistono casi per cui branch test e condition test non sono inclusivi; è sufficiente ragionare sulla condizione di branch definita dall’or logico per questo motivo i casi di test sufficienti a soddisfare un criterio non sono validi per l’altro e viceversa; è quindi ragionevole impostare una strategia di test basata sulla composizione dei due criteri: i dati di test sono definiti in modo che sia tutte le decisioni presenti nel programma che tutte le condizioni di cui sono composte assumano almeno una volta entrambi i valori di vero e falso.

I criteri a cui sono ispirati i test descritti finora non trattano cicli: è possibile ottenere dati di test che esercitano tutti i comandi, le decisioni e le condizioni percorrendo ogni ciclo una sola volta. È tuttavia comune che un difetto causi un malfunzionamento solo in seguito a un certo numero di ripetizioni di un ciclo, come ad esempio un banale Obi-Wan (gli errori “di uno” nell’indicizzazione dei vettori o nelle condizioni di disuguaglianza) o un più subdolo memory leak. Per trattare i cicli è necessario considerare i cammini nel grafo di flusso del programma:

  1. test dei cammini (path test); il test è progettato in modo da percorrere tutti i cammini plausibili nel grafo di flusso; questo criterio, già in assenza di cicli vede una crescita potenzialmente esponenziale del numero di casi di test al crescere del numero di decisioni e in presenza di cicli ha valore puramente teorico: applicarlo direttamente significa provocare tutte le possibili esecuzioni del programma;
  2. n-test dei cicli; anche per programmi molto semplici, il numero dei cammini percorribili può essere assai alto o anche non limitabile a priori, implicando costi troppo elevati per l’esecuzione dei test. Questo criterio, rispetto al precedente, introduce un limite al numero di volte che un ciclo può essere percorso. Non esiste un metodo per determinare il numero ottimale di iterazioni per i cicli di un programma: è lasciato all’esperienza del progettista dei test individuare il miglior compromesso fra la profondità dei test e il loro costo.

Inizialmente l’analisi del flusso dei dati è stata utilizzata nei compilatori a supporto delle tecniche di ottimizzazione del codice. Più recentemente, studi statistici hanno confermato le ipotesi formulate dai teorici circa i promettenti legami fra il test basato sull’esercizio dei dati e la rilevazione dei malfunzionamenti di un programma.
I criteri fondati sul flusso dei dati hanno come obiettivo principale le definizioni e l’uso delle variabili nel programma. È da notare che, in questa terminologia, con “definizione” di una variabile si intende l’assegnamento, la scrittura cioè, mentre con “uso” si intende la lettura del suo valore. I più noti test basati su questi criteri sono:

  1. test delle definizioni; i test progettati secondo questo criterio esercitano, per ogni definizione di variabile, almeno un cammino nel grafo di flusso che ne contenga un uso;
  2. test degli usi; è una forma più raffinata del precedente test: per ogni definizione di variabile il test deve esercitare almeno un cammino nel grafo di flusso che contenga un uso prima di altre definizioni della stessa variabile; obiettivo di questo raffinamento, la cui applicabilità dipende anche dalla struttura del programma, è ridurre il numero di casi di test e circoscrivere il difetto rispetto al malfunzionamento.
  3. test di tutti gli usi nelle condizioni / di alcuni usi nei calcoli; con questo test (in originale noto come all-p-uses/some-c-uses) si fa distinzione fra gli usi di una variabile in un calcolo (c-use) o nella valutazione di una condizione (p-use, da predicate); per ogni variabile, per ogni sua definizione si devono esercitare tutti i percorsi che contengono puse; questo criterio può lasciare scoperte alcune definizioni, quelle per cui la variabile è usata solo in calcoli; al solito, il criterio che ispira questo test ha come obiettivo la scelta di un insieme di casi di test “rilevanti”, qui la scelta è basata sull’assunzione che i p-use siano più critici ai fini della rilevazione di malfunzionamenti.

I test ispirati ai criteri white-box producono un insieme di dati d’ingresso che è determinato dalla struttura del programma e che non ha legami diretti con le sue funzionalità. Questo significa che i casi di test che ne derivano possono essere molto distanti dalla distribuzione di probabilità dei dati d’ingresso del programma. È infatti frequente che una parte sostanziale di un programma sia destinata al trattamento di casi particolari: le statistiche più pessimiste parlano di un 80% del codice usato solamente nel 20% dei casi. In questo senso, i casi di test ottenuti dai criteri strutturali, da un punto di vista strettamente utilitarista, possono essere considerati inefficienti.
Un ulteriore limite alla convenienza dei test basati su criteri strutturali è il problema dell’oracolo. Dai procedimenti con cui sono costruiti i casi di test non si ricavano indicazioni circa l’insieme dei dati di output che identifica il corretto funzionamento del programma; può quindi essere costoso generare un oracolo per il confronto dei risultati dei test.

I criteri strutturali non sono impiegati nella progettazione dei controlli di sistema. La grana fine del controllo esercitato sul software e l’onere dell’analisi del codice per ricavare i casi di test rendono i test ispirati a criteri white-box particolarmente indicati per controlli su oggetti di più ridotte dimensioni. La progettazione dei test a livello di modulo è il principale campo di applicazione. In particolare, quando la programmazione è molto strutturata, è conveniente ridurre ulteriormente la grana del controllo e considerare come oggetti del test le parti di codice (procedure, funzioni, subroutine, ecc.) relative alla realizzazione delle singole funzionalità del modulo.

Pubblicato da Vito Lavecchia

Lavecchia Vito Ingegnere Informatico (Politecnico di Bari) Email: [email protected] Sito Web: https://vitolavecchia.altervista.org

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *