Che cos’è e come implementare una strategia di test software

Che cos’è e come implementare una strategia di test software

Che cos’è una strategia di test

Nel testing software, le famiglie di criteri black-box e white-box costituiscono lo “zoccolo duro” della teoria e della pratica della progettazione dei test. Tuttavia il test è una disciplina lungi dall’essere consolidata e non esistono regole o criteri che garantiscano la sicurezza dei risultati. Per questo motivo, da una parte vengono proposti sempre nuovi criteri di test, dall’altra, pur nel rispettabile intento di trovare il maggior numero di malfunzionamenti, non diminuisce la tendenza a usare tecniche artigianali basate soprattutto sul buon senso e sull’esperienza dei progettisti. Il risultato più concreto di questo fermento tecnologico è la formulazione di articolate strategie di test.

Che cos'è e come implementare una strategia di test software

Costruire una strategia di test

Una strategia comprende spesso sia criteri di tipo funzionale (black-box) che criteri di tipo strutturale (white-box). Una prima famiglia di strategie deriva, abbastanza naturalmente, dall’idea di fondere gli approcci black-box e white-box; in letteratura troviamo questo approccio citato col nome di criteri gray-box. Una strategia di tipo gray-box prevede di testare il programma conoscendo i requisiti ed avendo una limitata conoscenza della realizzazione, per esempio conoscendo solo l’architettura. Un’altra strategia gray-box propone di progettare il test usando criteri funzionali e quindi di usare le misure di copertura (si veda la sezione “Valutazione dei test”) dei criteri strutturali per valutare l’adeguatezza del test: si può conoscere quanto del codice del programma è stato esercitato. Il seguente approccio di tipo gray-box, che proponiamo a titolo di esempio, è stato suggerito da Myers:

  1. un primo test è progettato utilizzando il grafo causa-effetto, questo consente anche di eseguire un’analisi accurata degli ingressi e delle uscite per verificare la completezza funzionale del programma;
  2. il grafo causa-effetto, realizzato al passo precedente, fornisce informazioni utili per determinare una partizione del dominio dei dati d’ingresso che sarà usata per integrare il test precedente applicando il criterio dei valori di frontiera;
  3. i progettisti in base alla loro esperienza, all’analisi funzionale svolta fino a questo punto nella progettazione dei test precedenti, al comportamento e agli eventuali malfunzionamenti del programma evidenziati dai test, sono chiamati a formulare delle ipotesi di malfunzionamento (error guessing, vedi più avanti) e a integrare di conseguenza i casi di test;
  4. si usa, infine, la struttura del programma per stabilire, alla luce dei criteri white-box, se i test realizzati ai passi precedenti hanno esercitato a sufficienza il codice. In caso contrario si possono sviluppare ulteriori casi di test.

Una strategia diversa è rappresentata dal test mutazionale. La tecnica si applica in congiunzione con altri criteri di test: nella sua formulazione è prevista infatti l’esistenza, oltre al programma da controllare, anche di un insieme di test già realizzati. La strategia prevede di introdurre modifiche controllate nel programma originale. Le modifiche riguardano in genere l’alterazione del valore delle variabili e la variazione delle condizioni booleane. I programmi così ottenuti, e scorretti, di regola, rispetto alle specifiche, sono definiti mutanti. L’insieme dei test realizzati precedentemente viene quindi applicato, senza modifiche, a tutti i mutanti e i risultati confrontati con quelli degli stessi test eseguiti sul programma originale.
Questa strategia è adottata con obiettivi diversi. Può essere usata per favorire la scoperta di malfunzionamenti ipotizzati: intervenire sul codice può essere più conveniente rispetto alla generazione di casi di test ad hoc. Il test mutazionale risulta inoltre utile per valutare l’efficacia dell’insieme di test, controllando se “si accorge” delle modifiche introdotte sul programma originale. La strategia può infine essere adottata per cercare indicazioni circa la localizzazione dei difetti la cui esistenza è stata denunciata dai test eseguiti sul programma originale.
L’uso della tecnica è limitato dal gran numero di mutanti che possono essere definiti, dal costo della loro realizzazione, e soprattutto dal tempo e dalle risorse necessarie a eseguire i test sui mutanti e a confrontare i risultati. Una buona conoscenza del codice aggiunta all’esperienza nell’ipotizzare i malfunzionamenti sono elementi necessari per introdurre un piccolo numero di modifiche mirate. Per questi motivi, il test mutazionale è usato spesso in congiunzione con criteri strutturali, adottati per la realizzazione dei test sul programma originale.

Una strategia applicabile al controllo dei programmi in evoluzione è quella nota in letteratura con il nome di test di regressione (o, a seconda dei testi, di non regressione). La strategia ha l’obiettivo di controllare se, dopo una modifica apportata per una necessità di sviluppo, il software è regredito, se cioè siano stati introdotti dei difetti non presenti nella versione precedente alla modifica. La strategia consiste nel riapplicare al software modificato i test progettati per la sua versione originale e confrontare i risultati. È giustificato parlare di strategia perché il test di regressione deve essere previsto e pianificato: è necessario mantenere i risultati e la documentazione di ogni esecuzione dei test ed è conveniente, visto il riuso che se ne farà, investire maggiori risorse nella progettazione della prima batteria di test. In generale il riuso diretto dei test è un evento piuttosto raro, più comunemente si assiste a un’evoluzione parallela del software e dei suoi test.

Il test di regressione è una strategia che trova applicazione in diversi scenari della produzione di software. Un primo caso, probabilmente il più comune, è la manutenzione software. Il prodotto viene consegnato insieme a una batteria di test: in seguito ad ogni intervento di manutenzione i test sono applicati per verificare che il programma non sia regredito. Di fatto, però, con il tempo, il susseguirsi di interventi di manutenzione adattiva e soprattutto perfettiva (e non monotona) rendono la batteria di test obsoleta. Un caso diverso riguarda i processi di sviluppo software basati su un ciclo di vita evolutivo. Il prodotto è realizzato attraverso una serie di prototipi; i test, soprattutto mirati alle funzionalità del prodotto, sono sviluppati insieme al primo prototipo e accompagnano l’evoluzione del prodotto, controllando in particolare che ogni passo evolutivo non abbia introdotto nuovi difetti. Per certi aspetti simile è l’uso dei test di regressione durante l’integrazione di un sistema software condotta secondo la strategia top-down.

In molti casi il malfunzionamento di un sistema è dovuto a errori che coinvolgono le interfacce dei moduli. Esistono quindi delle strategie di test che cercano di esercitare un sistema con l’obiettivo di mettere in evidenza i malfunzionamenti dovuti a questo tipo di errori. In altre parole si tratta di una rivisitazione dei criteri strutturali in termini dell’architettura di un sistema invece che del codice di un programma. I test di interfaccia sono basati su una classificazione degli errori commessi nella definizione delle interazioni fra i moduli:

  1. errore di formato; i parametri di invocazione o di ritorno di una funzionalità sono sbagliati per numero o per tipo; è un errore piuttosto frequente, ma fortunatamente è anche l’errore che i moderni linguaggi di programmazione e relativi mezzi di sviluppo, compilatori e linker, permettono di rilevare automaticamente con controlli statici;
  2. errore di contenuto; i parametri di invocazione o di ritorno di una funzionalità sono sbagliati per valore; è il caso in cui i moduli si aspettano argomenti il cui valore deve rispettare ben precisi vincoli; si va da parametri non inizializzati (ad esempio puntatori nulli) a strutture dati inutilizzabili (ad esempio un vettore non ordinato passato a una procedura di ricerca binaria);
  3. errore di sequenza o di tempo; in questo caso è sbagliata la sequenza con cui è invocata una serie di funzionalità, singolarmente corrette; nei sistemi dipendenti dal tempo possono anche risultare sbagliati gli intervalli temporali trascorsi fra un’invocazione e l’altra o fra un’invocazione e la corrispondente restituzione dei risultati.

Gli errori sono dovuti generalmente a una cattiva progettazione di dettaglio in conseguenza della quale si hanno specifiche di modulo lacunose o ambigue. In questo caso i moduli, presi singolarmente, rispettano le specifiche e i test di modulo non possono rilevare l’esistenza di un difetto. Più raro è il caso di specifiche di modulo corrette, cui però, per la scarsa importanza che viene riservata loro in un processo di sviluppo “rilassato”, non si è fatta la dovuta attenzione durante la codifica dei moduli. Qui i moduli, anche singolarmente non rispettano le specifiche e i test di modulo avrebbero dovuto rilevare la presenza di difetti.

I test di interfaccia sono progettati in base agli errori che si suppone siano presenti nel sistema, cercando in particolare di esercitare i moduli affinché siano riprodotte le situazioni tipiche degli errori di contenuto e di sequenza o di tempo. I criteri di progettazione tipici del test di interfaccia sono adottati soprattutto a supporto delle strategie di integrazione, in particolare per i test su tutto il sistema nelle strategie d’ispirazione top-down e come test ai vari livelli d’integrazione in quelle bottom-up.

Non sempre è possibile ottenere un oracolo nei limiti di tempo e di risorse previsti da un processo di sviluppo. In questi casi è pratica comune operare una semplificazione dei casi di test a quelli per i quali è possibile determinare, con esattezza e semplicità, i risultati attesi. Questa semplificazione può essere applicata con successo quando, in base alla conoscenza algoritmica del programma, dalla correttezza del comportamento sui casi “semplici” possiamo dedurre, con un buon margine di probabilità, la sua correttezza su tutti i casi.

Per concludere questo articolo sulla strategia di test software, è bene ricordare infine che in molti casi pratici l’asse portante di una strategia di test è del tutto empirico. I vari criteri, metodi e suggerimenti sono di fatto usati a supporto di una “tecnica” principale detta previsione degli errori e basata sul tentativo di indovinare (in inglese si parla proprio di error guessing) gli errori commessi dai professionisti che hanno partecipato alla realizzazione del software. Questa tecnica, che fa affidamento sulle incompetenze note di progettisti e programmatori, consiste nel formulare ipotesi sulla realizzazione di un programma e sui possibili malfunzionamenti che ne possono essere conseguenza: i casi di test sono progettati di conseguenza. La tecnica è efficace proporzionalmente all’esperienza e alle competenze dei professionisti che la adottano e, sebbene molto usata e spesso fruttuosa, non dovrebbe essere contemplata fra le tecniche ingegneristiche che, per definizione, devono essere documentabili e replicabili.

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 *