Buona parte della programmazione prevede l'acquisizione di elementi utilizzati più volte in passato e il relativo
utilizzo in un contesto differente. Tali elementi sono tipici di alcune strutture di dati delle classi (ad esempio gli
elenchi collegati, le tabelle hash o i database relazionali) o operazioni (come la ricerca, l'ordinamento, la creazione di file temporanei o
la visualizzazione di una finestra del browser). Ad esempio, due database relazionali di un cliente presenteranno
numerose caratteristiche stereotipate.
L'aspetto interessante di tali stereotipi è che hanno influenzato gli errori. Le persone non inventano modi nuovi per inserire un elemento in modo
non corretto in un elenco con duplice collegamento. Tendono invece a commettere gli stessi errori fatti in precedenza
da loro stesse e da altri. Un programmatore che visualizza una finestra del browser potrebbe commettere uno di questi
errori stereotipati:
-
crea una nuova finestra invece di utilizzarne una già aperta
-
non rende visibile una finestra del browser oscurata o ridotta
-
utilizza Internet Explorer quando l'utente ha scelto un browser predefinito differente
-
non verifica se JavaScript è abilitato
Dal momento che gli errori sono stereotipati, anche le proposte di test che li rilevano lo sono. Inserire tali proposte di test nel
relativo catalogo in modo da poterle riutilizzare.
Uno dei pregi di un catalogo è che una singola proposta di test può risultare utile per individuare più errori
sottostanti. Di seguito viene riportato un esempio di una proposta che rileva due errori.
Il primo errore era presente in un compilatore C. Tale compilatore ha acquisito le opzioni della riga comandi, come ad
esempio "-table" o "-trace" o "-nolink". E' stato possibile abbreviare le opzioni nel relativo formato univoco più
breve. Ad esempio, "-ta" aveva lo stesso significato di "-table". Tuttavia, "-t" non era consentito, perché era
ambiguo: poteva indicare sia "-table" che "-trace".
Internamente, le opzioni della riga comandi erano memorizzati in una tabella simile alla seguente:
Non appena un'opzione veniva individuata nella riga comandi, veniva ricercata nella tabella. Essa corrispondeva se era
il prefisso di qualsiasi voce della tabella; ossia "-t" corrispondeva a "-table". Dopo che era stata trovata una
corrispondenza, il resto della tabella veniva esaminato alla ricerca di un'altra corrispondenza. Un'altra
corrispondenza sarebbe stata un errore, perché avrebbe indicato ambiguità.
Il codice che effettuava la ricerca era simile al seguente:
for (first=0; first < size; first++)
{
if (matches(entry[first], thing_sought))
{ /* at least one match */
for(dup=first+1; dup < size; dup++) /* search for another */
if (matches(entry[dup], thing_sought)) /* extra match */
break; /* error out */
return first;
}
}
return -1; /* Not found or ambiguity */
E' evidente il problema? E' piuttosto sottile.
Il problema è l'istruzione break. E' progettata per derivare dal loop esterno che la racchiude in caso di rilevazione
di una corrispondenza duplicata, ma in realtà deriva dal loop più interno. L'effetto che ne deriva è lo stesso che si
otterrebbe in caso di mancata rilevazione di una seconda corrispondenza: viene restituito l'indice della prima
corrispondenza.
Tenere presente che tale errore può essere rilevato solo se, per l'opzione ricercata, venissero individuate due
corrispondenze nella tabella, come nel caso di "-t".
Viene ora riportato un secondo errore, completamente differente.
Il codice acquisisce una stringa. Lo scopo è sostituire l'ultimo '=' nella stringa con un '+'. Se non è presente alcun
'=', non viene eseguita alcuna azione. Il codice utilizza la routine della libreria C standard strchr. Ecco il codice:
ptr = strchr(string, '='); /* Trova l'ultimo = */ if (ptr != NULL_CHAR) *ptr = '+';
Anche in questo caso, il problema è sottile.
La funzione strchr restituisce la prima corrispondenza nella stringa, non l'ultima. La
funzione corretta è strrchr. Il problema era più che altro un errore tipografico.
(In realtà, il vero problema sottostante è che non è consigliabile inserire due funzioni che differiscono solo per un
carattere in una libreria standard).
Questo errore può essere individuato solo quando sono presenti due o più segni di uguale nell'input. Ossia:
-
"a=b" restituisce il risultato corretto, "a+b".
-
"noequals" restituisce il risultato corretto, "noequals".
-
"a=b=c" restituisce erroneamente "a+b=c", non il risultato corretto "a=b+c"
L'aspetto utile e interessante in questo caso è che sono stati trattati due errori con cause principali completamente
differenti errore tipografico, mancata comprensione di un costrutto C) e differenti manifestazioni nel codice (chiamata
la funzione sbagliata, utilizzo non corretto dell'istruzione break) che è possibile individuare tramite la
stessa proposta di test (ricerca di un elemento che si verifica due volte).
Cosa contribuisce a creare un buon catalogo?
-
Contiene una serie limitata di proposte di test che possono rilevare una serie molto più ampia di errori
sottostanti.
-
E' semplice e rapido da leggere (essenziale). Dovrebbe poter essere semplice saltare le proposte di test non
rilevanti per la propria situazione.
-
Contiene solo proposte di test che verranno utilizzate. Ad esempio, qualcuno che non ha mai a che fare con i
browser Web non dovrebbe essere obbligato a ignorare le proposte di test relative a programmi che utilizzano i
browser web. Qualcuno che lavora su software dei giochi prediligerà un catalogo più piccolo rispetto a qualcuno che
lavora su software critici per la sicurezza. La persona che si occupa di giochi può permettersi si concentrarsi
solo sulle proposte di test con la probabilità più elevata di rilevazione degli errori.
Stabilite queste regole, la soluzione migliore è disporre di più cataloghi. Alcuni dati e operazioni sono comuni a ogni
programmazione, quindi le relative proposte di test possono essere inserite in un catalogo utilizzato da tutti i
programmatori. Altre sono specifiche per un particolare dominio, quindi le relative proposte di test possono essere
inserite in un catalogo di proposte di test specifiche di un dominio.
Un catalogo di esempio (Get Adobe Reader), utilizzato nel seguente esempio, è
ottimo per iniziare. Proposte di test per combinazioni di AND e OR fornisce un altro esempio.
Ecco come è possibile utilizzare il catalogo di esempio. Si supponga che si sta implementando questo metodo:
void applyToCommonFiles(Directory d1, Directory d2, Operation op);
applyToCommonFiles acquisisce due directory come argomenti. Quando un file nella prima directory
ha lo stesso nome di un file nella seconda, applyToCommonFiles esegue la stessa operazione su
tale coppia di file. Arriva fino alle sottodirectory.
Il metodo per l'utilizzo del catalogo è la relativa scansione esaminando le intestazioni principali che corrispondono
alla propria situazione. Considerare le proposte di test in ogni intestazione per verificare se sono di rilievo e
quindi trascrivere quelle scelte in un elenco di proposte
di test.
Nota: è possibile che questa descrizione dettagliata faccia apparire l'uso del catalogo un'operazione laboriosa.
In realtà occorre più tempo per leggere le informazioni sulla creazione dell'elenco di controllo che per crearne
effettivamente uno.
Quindi, nel caso di applyToCommonFiles, è possibile applicare il catalogo nel modo descritto in
tutto il resto della presente sezione.
La prima voce è per Ogni oggetto. E' possibile che uno degli argomenti sia un puntatore nullo? Questa è una
questione del contratto tra applyToCommonFiles e i propri chiamanti. Il contratto potrebbe
essere che il chiamante non acceda a un puntatore nullo. In caso contrario, non è possibile affidarsi alla funzionalità
prevista: applyToCommonFiles può eseguire qualsiasi azione. In tal caso, nessun test è
appropriato, perché nessuna azione di applyToCommonFiles può essere considerata sbagliata.
Tuttavia, se applyToCommonFiles è necessario per ricercare i puntatori nulli, la proposta del
test risulterebbe utile. Si presupponga l'ultima ipotesi, che consente il seguente elenco delle proposte di test
iniziale:
-
d1 è nullo (caso di errore)
-
d2 è nullo (caso di errore)
-
op è nullo (caso di errore)
La successiva voce del catalogo è Stringhe. I nomi dei file sono stringhe e vengono confrontati per verificare
se corrispondono. La proposte di verifica con la stringa vuota ("") non sembra utile. Presumibilmente verranno
utilizzate alcune routine standard di confronto tra stringhe e gestiranno correttamente le stringhe vuote.
Ma se occorre confrontare delle stringhe, cosa avviene con le maiuscole/minuscole? Si supponga che d1 contiene un file denominato "File". d2 contiene un file denominato "file".
Tali file corrisponderanno? In UNIX, la risposta è chiaramente no. In Microsoft® Windows®, probabilmente è sì. Ecco
un'altra proposta di test:
-
I file corrispondono nelle due directory, ma le maiuscole/minuscole dei nomi sono diverse.
Tenere presente che questa proposta di test non è scaturita direttamente dal catalogo. Tuttavia, il catalogo ha
attirato la nostra attenzione su un particolare aspetto del programma (nomi file come stringhe) e la nostra creatività
ha suggerito un'altra idea. E' importante non utilizzare il catalogo in modo eccessivamente chiuso, ma utilizzarlo
invece come tecnica di brain storming, come una maniera per ispirare nuove proposte.
La successiva voce è Raccolte. Una directory è una raccolta di file. Molti programmi che gestiscono le
connessioni vengono interrotti sulla raccolta vuota. Alcuni che gestiscono la raccolta vuota o le raccolte con numerosi
elementi vengono interrotti su raccolte con un solo elemento. Quindi queste proposte sono utili:
-
d1 è vuoto
-
d2 è vuoto
-
d1 contiene esattamente un file
-
d2 contiene esattamente un file
L'idea successiva è utilizzare una raccolta della massima dimensione possibile. applyToCommonFiles viene generalmente utilizzato in directory di dimensioni limitate. Poi arriva un
qualsiasi utente che lo applica a due alberi di directory enormi, contenenti migliaia di file, solo per verificare che
il programma è grottescamente inefficiente in termini di memoria e non può gestire tale caso realistico.
A questo punto, la verifica della massima dimensione assoluta per una directory non è importante; tale directory deve
semplicemente avere le dimensioni adatte su cui un utente può eseguire i test. Tuttavia, si dovrebbe eseguire un
qualche test con un numero superiore a tre file in una directory:
-
d1 contiene moltissimi file
-
d2 contiene moltissimi file
La proposta di test finale (elementi duplicati) non si applica alle directory di file. Ossia, se si dispone di una
directory con due file che hanno lo stesso nome, il problema è indipendente da applyToCommonFiles- il proprio file system è danneggiato.
La successiva voce del catalogo è Ricerca. Tali proposte possono essere convertite in termini di applyToCommonFiles come il seguente:
-
d1 e d2 non hanno alcun file in comune (tutti i nomi sono differenti)
-
d1 e d2 hanno esattamente un file in comune (dal punto di vista alfabetico, è l'ultimo elemento nella directory)
-
d1 e d2 hanno più di un file in comune
La proposta di test finale controlla applyToCommonFiles. Esegue la restituzione non appena
rileva la prima corrispondenza? La prima osservazione tra parentesi nella proposta di test precedente presuppone che il
programma recuperi l'elenco di file in una directory utilizzando una routine di libreria che li restituisce in ordine
alfabetico. In caso contrario, potrebbe essere meglio utilizzare l'ultimo come corrispondenza. Prima di dedicare del
tempo a rilevare la modalità con cui i file vengono ordinati, chiedersi quanto è probabile che la collocazione
dell'elemento corrispondente per ultimo consenta la rilevazione dei difetti in modo più semplice. La collocazione di un
elemento per ultimo in una raccolta è più utile se il codice esamina la raccolta in base a un indice. Se utilizza un
iteratore, è molto improbabile che l'ordine abbia importanza.
Si esamini un'altra voce nel catalogo di esempio. La voce strutture collegate ricorda che si stanno confrontando
alberi di directory, non solo raccolte piatte di file. La decisione delle modalità di test di applyToCommonFiles rende obbligatorio il confronto dell'incompletezza della relativa descrizione.
Se la struttura della directory appare come la seguente:
Figura 1: Struttura di una directory
consente a applyToCommonFiles di andare a fondo nella directory Cdir? Ciò
non sembra avere molto senso. Non può esservi alcuna corrispondenza con gli elementi nell'altro albero della directory.
In effetti, sembra come se i file nelle sottodirectory potessero corrispondere solo se i nomi della sottodirectory
corrispondono. Ossia, si supponga di avere questa struttura della directory:
Figura 2: Struttura di una directory secondaria
I file denominati "File" non corrispondono perché si trovano in sottodirectory differenti. Le sottodirectory andrebbero
analizzate solo se hanno lo stesso nome in entrambe le ubicazioni: d1 d2.
Ciò porta alle seguenti proposte di test:
-
una determinata sottodirectory in d1 non è presente in d2 (nessuna discesa)
-
una determinata sottodirectory in d2 non è presente in d1 (nessuna discesa)
-
una determinata sottodirectory è presente sia in d1 che in d2 (discesa)
Ma, a questo punto, sorgono altre domande. L'operazione (op) deve essere applicata alle
sottodirectory corrispondenti o solo ai file corrispondenti? Se viene applicata alle sottodirectory, deve essere
applicata prima o dopo la discesa? Ciò fa la differenza se, ad esempio, l'operazione elimina il file o la directory
corrispondente. Per tale ragione, occorre consentire che l'operazione modifichi la struttura della directory? E
in modo più specifico: qual è la funzionalità corretta di applyToCommonFiles se ciò avviene? (E'
la stessa problematica scaturita dagli iteratori).
Questo genere di domande generalmente sorgono quando si legge attentamente una descrizione del metodo di creazione
delle nuove proposte di test. Ma lasciamole un attimo da parte. Qualsiasi siano le risposte, dovranno essere proposte
di test specifiche, che controllano se il codice implementa correttamente le risposte.
Ritorniamo al catalogo. Non sono ancora state considerate tutte le relative proposte di test. La prima, vuota (nessun
elemento presente nella struttura), richiede una directory vuota. Tale directory è già stata ottenuta dalla voce
Raccolte. E' stata anche ricevuta la struttura minima non vuota, che è una directory con un singolo
elemento. Questa sorta di ridondanza non è rara, ma è semplice ignorarla.
Cosa succede nel caso di una struttura circolare? Le strutture di directory non possono essere circolari, una
directory non può essere contenuta all'interno di una di tali discendenti o all'interno di se stessa... oppure può
esserlo? Cosa avviene in caso di collegamenti rapidi (in Windows) o di collegamenti simbolici (in UNIX)? Se è presente
un collegamento nell'albero di directory in d1, che punta a d1, è
necessario che applyToCommonFiles continui la discesa all'infinito? La risposta potrebbe
condurre a una o più nuove proposte di test:
-
d1 è circolare a causa di collegamenti o di collegamenti simbolici
-
d2 è circolare a causa di collegamenti o di collegamenti simbolici
A seconda della funzionalità corretta, è possibile creare ulteriori proposte di test.
Infine che dire della complessità maggiore di uno? Le precedenti proposte di test verificheranno l'esecuzione di
test tramite discesa in uno dei livelli della sottodirectory, ma occorrerà controllare che applyToCommonFiles continui la discesa:
-
discenda attraverso numerosi livelli (>1) delle sottodirectory di d1
-
discenda attraverso numerosi livelli (>1) delle sottodirectory di d2
Come indicato in precedenza, il catalogo generico non conterrà tutte le proposte di test necessarie. Ma i cataloghi
specifici di un dominio non sono stati pubblicati al di fuori delle società che li hanno creati. Se
qualcuno ne avesse bisogno, sarà costretto a crearlo. Di seguito vengono riportati alcuni suggerimenti.
-
Non inserire nel catalogo le proprie speculazioni sulla validità delle proposte per la rilevazione gli errori.
Ricordare che ogni proposta di test inserita nel catalogo costa tempo e denaro:
-
il proprio tempo per la gestione del catalogo
-
il tempo di altri programmatori per pensare alla proposta di test
-
probabilmente il tempo di altri programmatori per l'implementazione di un test
Aggiungere solo proposte che dispongono di un record di traccia dimostrato. Sarebbe opportuno potere puntare ad
almeno un errore reale da individuare tramite la proposta di test. Idealmente, l'errore dovrebbe essere uno che è
stato trascurato da un'altra verifica; ossia, uno segnalato dal campo. Un buon modo per creare i cataloghi è
esaminare il database di bug della propria società e chiedersi in che modo sarebbe stato possibile individuare
prima l'errore.
-
-
Non ha senso lavorare se la creazione e la gestione di un catalogo di proposte di test è un'attività da effettuare
nel proprio tempo libero. Sarà necessario del tempo da assegnare a questa attività come se si trattasse di una
qualsiasi altra attività. Si consiglia la creazione e la gestione del proprio catalogo delle proposte di test
durante Compito: Miglioramento delle risorse di test.
|