Linea guida: Proposte di test per valori booleani e boundary
Le proposte di test si basano su errori di test plausibili e su come è possibile rilevare tali errori nel modo migliore. Questa linea guida spiega come sviluppare le proposte di test per le espressioni booleane e per le condizioni di boundary.
Relazioni
Elementi correlati
Descrizione principale

Introduzione

Le proposte di test si basano su modelli di errore, nozioni relative agli errori plausibili nel software e alle modalità con cui è possibile individuare tali errori. Questa linea guida spiega come creare proposte di test da espressioni booleane e relazionali. Innanzitutto motiva le tecniche esaminando il codice, quindi descrive come applicarle se il codice non è ancora stato scritto o non è in qualche modo disponibile.

Espressioni booleane

Considerare il seguente frammento di codice, preso da un sistema (immaginario) per la gestione della detonazione di una bomba. Fa parte del sistema di sicurezza e controlla se viene rispettato il pulsante che consente di far esplodere la bomba.

if (publicIsClear || technicianClear) {
    bomb.detonate();
}

Il codice non è corretto. || dovrebbe essere &&. Tale errore avrà effetti deleteri. Anziché far esplodere la bomba quando il pubblico e il tecnico della bomba sono chiari, il sistema eseguirà la detonazione quando uno dei due è chiaro.

Quale test rileverà questo bug?

Si consideri un test in cui il pulsante viene premuto quando sia il tecnico che il pubblico sono chiari. Il codice consentirà la detonazione della bomba. Ma, e questo è l'aspetto importante, il codice corretto (quello che utilizza &&) farebbe lo stesso. Quindi il test è inutile per la rilevazione di questo errore.

In modo simile, questo codice errato funziona correttamente quando sia il pubblico che il tecnico sono nei pressi della bomba: la bomba non viene detonata.

Per rilevare il bug, è necessario che si verifichi un caso in cui il codice, così scritto, si esprime in modo differente rispetto al codice che andava scritto. Ad esempio, il pubblico deve essere chiaro, ma il tecnico della bomba è ancora accanto ad essa. Ecco tutti i test sotto forma di tabella:

publicIsClear

technicianClear

Il codice come è stato scritto...

Il codice corretto avrebbe dovuto...

 

true

true

detona

detonare

il test è inutile (per questo errore)

true

false

detona

non denotare

test utile

false

true

detona

non denotare

test utile

false

false

non detona

non denotare

il test è inutile (per questo errore)


Entrambi i due test centrali sono utili per la rilevazione di questo particolare errore. Tenere presente, tuttavia, che sono ridondanti: dal momento che ambedue rileveranno l'errore, non sarà necessario eseguirli entrambi.

Vi sono altri modi in cui l'espressione potrebbe essere sbagliata. Di seguito sono riportati due elenchi di errori comuni nelle espressioni booleane. Gli errori a sinistra vengono rilevati tutti tramite la tecnica discussa in questa sezione. Gli errori a destra potrebbero non esserlo. Quindi questa tecnica non rileva tutti gli errori, ma è comunque utile.

Rilevati errori

Errori probabilmente non rilevati

Utilizzo dell'operatore sbagliato: a || b dovrebbe essere a&& b Utilizzata variabile sbagliata: a&&b&&c dovrebbe essere a&& x&&d
Negazione omessa o non corretta: a||b dovrebbe essere !a||b o ! a||b dovrebbe essere a||b L'espressione è troppo semplice: a&&b dovrebbe essere a&&b&&c
Le espressioni tra parentesi non sono configurate correttamente: a&&b||c dovrebbe essere a&&(b||c) Espressioni con più errori nella colonna di sinistra
L'espressione è eccessivamente complessa: a&&b&&c dovrebbe essere a&&b
(Questo errore non è così comune, ma è semplice da individuare con test utili per altri motivi).
 

In che modo vengono utilizzate tali proposte? Si supponga di avere un'espressione booleana del tipo a&&!b. E' possibile costruire una tabella di valori true/false simile alla seguente:

a

b

a&&!b
(codice come è scritto)

forse dovrebbe essere
a||!b

forse dovrebbe essere
! a&&!b

forse dovrebbe essere
a&&b

...

true

true

false

true

false

true

...

true

false

true

true

false

false

...

false

true

false

false

false

false

...

false

false

false

true

true

false

...


Se sono state valutate tutte le possibilità, si rileverà che la prima, la seconda e la quarta sono le uniche necessarie. La terza espressione non rileverà alcun errore che non possa essere rilevato da una delle altre, quindi non è necessaria. (Man mano che le espressioni si complicano, il risparmio dovuto a casi non necessari aumenta rapidamente).

Ovviamente, nessuno con un minimo di cervello creerebbe tale tabella. Fortunatamente, non è necessario farlo. E' semplice memorizzare i casi necessari per le espressioni semplici. (Consultare la sezione successiva). Per espressioni più complesse, ad esempio A&&B||C, consultare Proposte di test per combinazioni di AND e OR, che elenca le proposte di test per espressioni con due o più operatori. Per espressioni ancora più complesse, è possibile utilizzare un programma per generare le proposte di test.

Tabelle per espressioni booleane semplici

Se l'espressione è A&&B, testare con:

A

B

true

true

true

false

false

true


Se l'espressione è A||B, testare con:

A

B

true

false

false

true

false

false


Se l'espressione è A1 && A2 && ... && An, testare con:

A1, A2, ... e An sono tutti true

A1 è false, tutto il resto è true

A2 è false, tutto il resto è true

...

An è false, tutto il resto è true


Se l'espressione è A1 || A2 || ... || An, testare con:

A1, A2, ... e An sono tutti false

A1 è true, tutto il resto è false

A2 è true, tutto il resto è false

...

An è true, tutto il resto è false


Se l'espressione è A, testare con:

A

true

false


Quindi, quando è necessario testare a&&!b, è possibile applicare la prima tabella descritta in precedenza, invertire il senso di b (perché è stato negato) e acquisire questo elenco di Proposte di test:

  • A true, B false
  • A true, B true
  • A false, B false

Espressioni relazionali

Ecco un altro esempio di codice con un errore:

if (finished < required) {
    siren.sound();
}

< dovrebbe essere a <=. Tali errori sono abbastanza comuni. Come avviene con le espressioni booleane, è possibile costruire una tabella di valori di test e vedere quali rilevano l'errore:

terminati

richiesti

Il codice come è stato scritto...

Il codice corretto avrebbe dovuto...

1

5

fa suonare la sirena

ha fatto suonare la sirena

5

5

non fa suonare la sirena

ha fatto suonare la sirena

5

1

non fa suonare la sirena

non ha fatto suonare la sirena


Più generalmente, l'errore può essere rilevato ogni qualvolta terminati=richiesti. Da analisi di errori plausibili, è possibile ricavare tali regole per le proposte di test:

Se l'espressione è A<B o A>=B, testare con i seguenti elementi:

A=B

A leggermente inferiore a B


Se l'espressione è A>B o A<=B, testare con i seguenti elementi:

A=B

A leggermente superiore a B


Cosa significa "leggermente"? Se A e B sono numeri interi, A dovrebbe essere inferiore o superiore di un numero rispetto a B. Se sono numeri a virgola mobile, a dovrebbe essere un numero abbastanza vicino a B. (Probabilmente non è necessario che sia il numero a virgola mobile più vicino a B).

Regole per espressioni relazionali e booleane combinate

La maggior parte degli operatori relazionali si verificano in espressioni booleane, come nel seguente esempio:

if (finished < required) {
    siren.sound();
}

Le regole per le espressioni relazionali comporterebbero le seguenti proposte di test:

  1. terminati è uguale a richiesti
  2. terminati è leggermente inferiore a questa espressione:richiesti

Le regole per le espressioni booleane comporterebbero le seguenti proposte di test:

  1. terminati < richiesti dovrebbe essere true
  2. terminati < richiesti dovrebbe essere false

E' possibile che questa espressione: terminati è leggermente inferiore a questa espressione:richiesti, terminati < richiesti sia true, quindi non vi è ragione di scrivere l'ultima.

Se questa espressione è false non vi è ragione di scrivere: terminati è uguale a richiesti, , terminati < richiesti .

Se un'espressione relazionale non contiene alcun operatore booleano (&& e ||), ignorare il fatto che è anche un'espressione booleana.

La faccenda si complica leggermente con combinazioni di operatori booleani e relazionali, come la seguente:

if (count<5 || always) {
   siren.sound();
}

Dall'espressione relazionale, si ricava:

  • conteggio leggermente inferiore a 5
  • conteggio uguale a 5

Dall'espressione booleana, si ricava:

  • conteggio<5 true, sempre false
  • conteggio<5 false, sempre true
  • conteggio<5 false, sempre false

Tali espressioni possono essere combinate in tre ulteriori proposte di test più specifiche. (In questo caso, si noti che conteggio è un numero intero).

  1. conteggio=4, sempre false
  2. conteggio=5, sempre true
  3. conteggio=5, sempre false

Tenere presente che conteggio=5 viene utilizzato due volte. Potrebbe essere meglio utilizzarlo una sola volta, per consentire l'utilizzo di un altro valore, dopo tutto, che senso ha testare questa espressione con due 5: conteggio ? Non sarebbe meglio utilizzarla una sola volta con 5 e un'altra con un valore differente in modo che il risultato sia false? (Esempio: conteggio<5 Potrebbe esserlo, ma è rischioso provare. Il motivo è che è semplice commettere un errore. Si supponga di avere provato quanto segue:

  1. conteggio=4, sempre false
  2. conteggio=5, sempre true
  3. conteggio<5 false, sempre false

Si supponga che è presente un errore che è possibile rilevare soltanto con il seguente valore: conteggio=5. Ciò significa che il valore 5 produrrà "false" nell'espressione conteggio<5, quando il codice corretto avrebbe prodotto true. Tuttavia, il valore false viene immediatamente fatto seguire dal valore "sempre", che è true. Ciò significa che il valore dell'intera espressione è corretto, anche se il valore dell'espressione secondaria relazionale è sbagliato. L'errore non verrà rilevato.

L'errore viene rilevato se è l'altro conteggio=5 che rimane meno specifico.

Problemi simili si verificano quando l'espressione relazionale si trova a destra dell'operatore booleano.

Dal momento che è difficile sapere quali espressioni secondarie devono essere esatte e quali possono essere generali, è meglio renderle tutte esatte. L'alternativa è utilizzare il programma di espressioni booleane citato in precedenza. Esso produce proposte di test corrette per espressioni relazionali e booleane combinate arbitrarie.

Proposte di test senza codice

Come spiegato in Concetto: Test-first Design, è generalmente preferibile progettare i test prima di implementare il codice. Quindi, sebbene le tecniche siano motivate da esempi di codice, verranno generalmente applicate senza codice. In che modo?

Alcuni artefatti di progettazione, ad esempio i grafici di stato e i diagrammi di sequenza, utilizzano espressioni booleane come guardie. Questi casi sono semplici: aggiungono semplicemente le proposte di test dalle espressioni booleane all'elenco di controllo delle proposte di test dell'artefatto. Consultare Linea guida del prodotto di lavoro: Proposte di test per il grafico di stato e per i diagrammi di attività.

Il caso più complesso si verifica quando le espressioni booleane sono implicite anziché piuttosto esplicite. Ciò avviene spesso nelle descrizioni delle API. Di seguito ne viene riportato un esempio. Considerare questo metodo:

List matchList(Directory d1, Directory d1,
       FilenameFilter excluder);

La descrizione della funzionalità del metodo può essere letta in questo modo:

Restituisce un elenco dei nomi percorso assoluti di tutti i file visualizzati in entrambe le directory. Le sottodirectory vengono generate. [...] I nomi file che corrispondono a excluder vengono esclusi dall'elenco restituito. L'excluder si applica soltanto alle directory di livello più elevato, non ai nomi file nelle sottodirectory.

Le parole "e", "o" non compaiono. Ma quando viene incluso un nome file nell'elenco di restituzione? Quando compare nella prima directory e nella seconda directory e si trova in una directory di livello inferiore o non viene escluso in modo specifico. Nel codice:

if (appearsInFirst && appearsInSecond &&
    (inLowerLevel || !excluded)) {
  add to list
}

Ecco alcune proposte di test per tale espressione, fornite in formato tabulare:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

false

false

false

true

false

false


L'approccio generale per la rilevazione di espressioni booleane implicite dal testo è, innanzitutto, elencare le azioni descritte (ad esempio "restituisce un nome corrispondente"). Quindi scrivere un'espressione booleana che descriva i casi in cui viene eseguita un'azione. Far derivare le proposte di test da tutte le espressioni.

Vi è anche spazio per un eventuale disaccordo in tale processo. Ad esempio, è possibile che una persona trascriva l'espressione booleana utilizzata in precedenza. Un altro potrebbe affermare che, in effetti, sono presenti due azioni distinte: prima il programma rileva i nomi corrispondenti e poi li filtra. Quindi, anziché un'espressione, ne sono presenti due:

rileva corrispondenza:
si verifica quando un file si trova nella prima directory e un file con lo stesso nome si trova nella seconda directory
filtra corrispondenza:
si verifica quando i file corrispondenti si trovano nel livello superiore e il nome corrisponde all'excluder

Questi differenti approcci possono generare proposte di test differenti e, quindi, test differenti. Ma le differenze non sono poi così importanti. Ciò significa che il tempo impiegato nel cercare di capire quale espressione sia corretta e nel tentare le alternative, potrebbe essere speso meglio su altre tecniche e per produrre ulteriori test. Per ulteriori informazioni sulle varie differenze che potrebbero esistere, proseguire con la lettura.

La seconda persona penserà due serie di proposte di test.

proposte di test sulla rilevazione di una corrispondenza:

  • file nella prima directory, file nella seconda directory (true, true)
  • file nella prima directory, file non presente nella seconda directory (true, false)
  • file non presente nella prima directory, file nella seconda directory (false, true)

proposte di test sul filtraggio di una corrispondenza (una volta che è stata rilevata):

  • i file corrispondenti si trovano nel livello superiore, il nome corrisponde all'excluder (true, true)
  • i file corrispondenti si trovano nel livello superiore, il nome non corrisponde all'excluder (true, false)
  • i file corrispondenti si trovano in un livello inferiore, il nome corrisponde all'excluder (false, true)

Si suppongano due serie di proposte di test combinate. Quelle nella seconda serie sono rilevanti solo quando il file è presente in entrambe le directory, quindi tali proposte possono essere combinate solo con la prima proposta nella prima serie. Tale situazione genera quanto segue:

file nella prima directory

file nella seconda directory

livello superiore

corrispondenza con l'excluder

true

true

true

true

true

true

true

false

true

true

false

true


Due delle proposte di test relative alla rilevazione di una corrispondenza non compaiono in tale tabella. E' possibile aggiungerle nel seguente modo:

file nella prima directory

file nella seconda directory

livello superiore

corrispondenza con l'excluder

true

true

true

true

true

true

true

false

true

true

false

true

true

false

-

-

false

true

-

-


Le celle vuote indicano che le colonne sono irrilevanti.

Questa tabella è ora abbastanza simile alla tabella della prima persona. La somiglianza può essere enfatizzata utilizzando la stessa terminologia. La tabella della prima persona contiene una colonna denominata "inLower" e quella della seconda persona ne ha una denominata "nel livello più elevato". Tali colonne possono essere convertite modificando leggermente il senso dei valori. In tal modo, si ottiene questa versione della seconda tabella:

appearsInFirst

appearsInSecond

inLower

excluded

true

true

false

true

true

true

false

false

true

true

true

true

true

false

-

-

false

true

-

-


Le prime tre righe sono identiche alla tabella della prima persona. Le ultime due differiscono solo nel fatto che questa versione non specifica i valori specificati dalla prima. Ciò deriva da una presupposizione relativa al modo in cui il codice è stato scritto. La prima ha presupposto un'espressione booleana complicata:

if (appearsInFirst && appearsInSecond &&
    (inLowerLevel || !excluded)) {
  add to list
}

La seconda presuppone espressioni booleane nidificate.

if (appearsInFirst && appearsInSecond) {
    // rilevata una corrispondenza.
    if (inTopLevel && excluded) {
// filtrarla
    }
}

la differenza tra le due è che le proposte di test per la prima rilevano due errori ignorati dalle proposte della seconda, perché tali errori non si applicano.

  1. Nella prima implementazione, è possibile che sia presente un errore tra parentesi. Le parentesi intorno a || sono corrette o sbagliate? Dal momento che la seconda implementazione non prevede alcuna parentesi e nessun ||, l'errore non può esistere.
  2. I requisiti del test per la prima implementazione verificano se la seconda espressione, &&, dovrebbe essere ||. Nella seconda implementazione, tale && esplicito è sostituito dall'implicito &&. Non vi è alcun errore ||-per-&& . (E' possibile che la nidificazione non sia corretta, ma tale tecnica non lo verifica).