Concetto: Elenco proposta di test
Un elenco di proposte di test è un elenco di proposte organizzate in ordine decrescente di importanza e associate a specifiche strategie di verifica utilizzate per creare test eseguibili.
Relazioni
Descrizione principale

Introduzione

Le informazioni utilizzate nella progettazione dei test vengono raccolte da varie fonti: modelli di progettazione, interfacce del classificatore, diagrammi di stato e il codice stesso. In un determinato momento, queste informazioni di origine sul documento devono essere convertite in test eseguibili:

  • input specific assegnati al software sottoposto a test
  • una particolare configurazione hardware e software
  • inizializzato in uno stato noto
  • previsti risultati specifici

E' possibile andare direttamente dalle informazioni del documento di origine ai test eseguibili, ma spesso è utile aggiungere una fase intermedia. In questa fase, le proposte di test vengono scritte in un elenco di proposte di test, utilizzato per creare test eseguibili.

Che cosa sono le proposte di test?

Una proposta di test (talvolta indicata come requisito del test) è una breve istruzione relativa a un test che è possibile eseguire. Come semplice esempio, considerare una funzione che calcola una radice quadrata e creare delle proposte di test:

  • fornire un numero leggermente inferiore a zero rispetto all'input
  • fornire zero come input
  • testare un numero che sia una radice quadrata perfetta, come 4 o 16 (il risultato è esattamente 2 o 4?)

Ognuna di tali idee può essere convertita prontamente in un test eseguibile con descrizioni esatte di input e dei risultati previsti.

Esistono due vantaggi in questa forma intermedia meno specifica:

  • le proposte di test sono più semplici da esaminare e più comprensibili rispetto ai test completi; è più semplice capire il ragionamento che si cela dietro tali proposte
  • le proposte di test supportano dei test più potenti, come descritto in seguito nell'intestazione Progettazione dei test tramite l'elenco

Tutti gli esempi relativi alla radice quadrata descrivono gli input, ma le proposte di test possono descrivere qualsiasi elemento di un test eseguibile. Ad esempio "stampa su una LaserJet IIIp" descrive un aspetto dell'ambiente di test da utilizzare per un test, come avviene per "test con database pieno", tuttavia, queste ultime proposte di test sono estremamente vaghe in se stesse: cosa si stampa nella stampante? Cosa si fa con il database completo? Esse, tuttavia, assicurano che non vengano dimenticate delle proposte importanti; proposte che verranno descritte in dettaglio in un secondo momento della progettazione di test.

Le proposte di test si basano spesso su modelli di errore; nozioni relative agli errori plausibili nel software e alle modalità con cui è possibile individuare tali errori. Ad esempio, considerare i boundary. E' consigliabile presupporre che la funzione di radice quadrata possa essere implementata in un modo simile al seguente:

double sqrt(double x) {
    if (x < 0)
      // errore di segnale
    ...

E' inoltre plausibile che < venga scritto in modo non corretto, ossia come <=. Questo errore è molto frequente, quindi vale la pena controllare. Non è possibile individuare l'errore se X assume il valore 2, perché sia l'espressione sbagliata (x<=0) che l'espressione corretta (x<0) utilizzeranno lo stesso ramo dell'istruzione if. In modo simile, se si assegna ad X il valore -5, non è possibile individuare l'errore. L'unico modo per rilevarlo è assegnare ad X il valore 0, che giustifica la seconda proposta di test.

In questo caso, il modello di errore è esplicito. In altri casi, è implicito. Ad esempio, ogni qualvolta un programma gestisce una struttura collegata, è consigliabile eseguire nuovamente il test rispetto a una struttura circolare. E' possibile che molti errori conducano a una struttura circolare mal gestita. Per gli scopi di verifica, non è necessario enumerarli; è sufficiente sapere che qualche errore è sufficiente perché valga la pena eseguire il test.

I seguenti collegamenti forniscono informazioni sull'acquisizione delle proposte di test da differenti tipi di modelli di errore. I primi due sono modelli di errore espliciti; l'ultimo utilizza dei modelli impliciti.

Questi modelli di errore possono essere applicati a differenti prodotti di lavoro. Ad esempio, il primo descrive a che servono le espressioni Booleane. E' possibile rilevare tali espressioni nel codice, nelle condizioni di guardia, nei grafici di stato, nei diagrammi di sequenza e nelle descrizioni del linguaggio naturale delle funzionalità del metodo (come quelle presenti in un'API pubblicata).

Di tanto in tanto è anche utile disporre di linee guida per prodotti di lavoro specifici. Consultare Linee guida: proposte di test per il grafico di stato e per i diagrammi di flusso.

Un determinato elenco di proposte di test potrebbe contenere proposte di test provenienti da diversi modelli di errore e tali modelli di errore potrebbero derivare da più prodotti.

Progettazione del test utilizzando l'elenco

Si supponga che è in corso la progettazione dei test per un metodo che ricerca una stringa in una raccolta sequenziale. Esso può rispettare o ignorare le maiuscole/minuscole nella propria ricerca e restituisce l'indice della prima corrispondenza trovata o -1 se non viene rilevata alcuna corrispondenza.

int Collection.find(String string,
            Boolean ignoreCase);

Ecco alcune proposte di test per questo metodo:

  1. trovata corrispondenza nella prima posizione
  2. trovata corrispondenza nell'ultima posizione
  3. nessuna corrispondenza rilevata
  4. rilevate due o più corrispondenze nella raccolta
  5. le maiuscole/minuscole vengono ignorate, ma non verrebbe stabilita alcuna corrispondenza se si rispettassero le maiuscole/minuscole
  6. le maiuscole/minuscole vengono rispettate; viene individuata una corrispondenza esatta
  7. le maiuscole/minuscole vengono rispettate; una stringa, con cui sarebbe stata rilevata una corrispondenza se le maiuscole/minuscole fossero state ignorate, viene saltata

Sarebbe semplice implementare questi sette test, uno per ogni proposta di test. Tuttavia, differenti proposte di test possono essere combinate in un singolo test. Ad esempio, il seguente test soddisfa le proposte di test 2, 6 e 7:

Configurazione: raccolta inizializzata su ["dawn", "Dawn"]
Richiamo: collection.find("Dawn", false)
Risultato previsto: il valore di ritorno è 1 (sarebbe 0 se non fosse stato ignorato "dawn")

Se le proposte di test non sono specifiche, è più semplice combinarle tra di loro.

E' possibile soddisfare tutte le proposte di test in tre test. Perché tre test che soddisfano sette proposte di test dovrebbero essere migliori di sette test separati?

  • Quando si crea un elevato numero di test semplici, è frequente creare il test N+1 copiando il test N e adattandolo affinché soddisfi la nuova idea di test. Il risultato, specialmente in un software più complesso, è che il test N+1 verifica il programma quasi nello stesso modo del test N. Esso percorre approssimativamente lo stesso percorso attraverso il codice.

    Un numero minore di test, ognuno che soddisfa diverse proposte di test, non consente un approccio di "copia e adattamento". Ogni test sarà in qualche modo differente rispetto all'ultimo, utilizzando il codice in maniere differenti e seguendo percorsi differenti.

    Perché ciò dovrebbe essere migliore? Se l'elenco di proposte di test fosse completo, con una proposta di test per ogni errore nel programma, la modalità di scrittura dei test non avrebbe alcuna importanza. Ma nell'elenco mancano sempre alcune proposte di test che potrebbero individuare i bug. Facendo in modo che ogni test esegua azioni differenti dall'ultima, aggiungendo cioè una varietà apparentemente non necessaria, si aumenta la possibilità che uno di tali test scovi un bug per un banale colpo di fortuna. Infatti, nei test più brevi e più complessi aumentano le possibilità di soddisfare una proposta di test di cui non si percepiva neanche la necessità.

  • Talvolta, quando si creano test più complessi, nuove proposte di test vengono alla mente. Ciò succede meno frequentemente con i test semplici, perché quasi tutte le azioni eseguite sono esattamente identiche a quelle dell'ultimo test, il che intorpidisce la mente.

Tuttavia, esistono delle ragioni che giustificano la mancata creazione di test complessi.

  • Se ogni test soddisfa una singola proposta di test e il test per la proposta 2 ha esito negativo, è subito chiara la causa più probabile: il programma non gestisce una corrispondenza nell'ultima posizione. Se un test soddisfa le idee 2, 6 e 7, è più arduo isolare l'errore.

  • I test complessi sono più difficili da comprendere e da gestire. L'intento del test è meno ovvio.

  • I test complessi sono più difficili da creare. La costruzione di un test che soddisfa cinque proposte di test spesso richiede più tempo rispetto alla costruzione di cinque test che soddisfano ognuno una proposta. Inoltre, è più semplice commettere degli errori, pensando di soddisfare tutte e cinque le proposte mentre se ne soddisfano solo quattro.

In pratica, occorre trovare un equilibrio ragionevole tra la complessità e la semplicità. Ad esempio, i primi test a cui si sottopone il software (generalmente i test iniziali) dovrebbero essere facili, semplici da comprendere e gestire e progettati per cogliere i problemi più ovvi. I test successivi dovrebbero essere più complessi, ma non al punto tale da non essere gestibili.

Una volta terminata una serie di test, è bene controllarli rispetto agli errori caratteristici di progettazione test discussi in Concetto: Verifica dello sviluppatore.

Utilizzo delle proposte di test prima della verifica

Un elenco di proposte di test è utile per analisi ed esami dei prodotti di lavoro della progettazione. Ad esempio, considerare questa parte di un modello di progettazione che mostra l'associazione tra le classi Reparto e Dipendente.

Immagine di esempio di un modello di progettazione

Figura 1: Associazione tra le classi Reparto e Dipendente

Le regole per la creazione delle proposte di test da tale modello impongono di considerare il caso in cui un reparto contiene molti dipendenti. Esaminando una progettazione e chiedendosi "cosa succede se, a questo punto, il reparto contiene molti dipendenti?", è possibile rilevare errori di progettazione o di analisi. Ad esempio, si potrebbe realizzare che è possibile trasferire un solo dipendente alla volta tra reparti. Ciò potrebbe essere un problema se l'azienda è incline a rapide riorganizzazioni in cui è necessario trasferire numerosi dipendenti.

Tali errori, ossia i casi in cui è stata trascurata una possibilità, vengono chiamati errori di omissione. Proprio come è avvenuto con gli errori stessi, dal proprio impegno di verifica sono stati probabilmente omessi i test che rilevano tali errori. Ad esempio, consultare [GLA81], [OST84], [BAS87], [MAR00] e altri studi che mostrano con quanta frequenza vengono commessi errori di omissione che sfociano poi nella distribuzione.

Il ruolo della verifica nelle attività di progettazione viene discusso ulteriormente in Concetto: Test-first Design.

Proposte di test e tracciabilità

La tracciabilità è una questione di compromessi. Vale la pena trattarla, considerato il relativo costo di gestione? Questa domanda verrà presa in considerazione durante l' Compito: Definizione delle esigenze di tracciabilità e di valutazione.

Quando vale la pena utilizzare la tracciabilità, è consuetudine ricondurre i test ai prodotti di lavoro che li hanno inspirati. Ad esempio, è possibile parlare di tracciabilità tra un'API e i relativi test. Se l'API cambia, è evidente quale test si dovrà modificare. Se il codice (che implementa l'API) cambia, è chiaro quali test occorre eseguire. Se un test risulta inspiegabile, è possibile individuare l'API che lo ha ispirato.

L'elenco di proposte di test aggiunge un altro livello di tracciabilità. E' possibile ricondurre un test alle proposte di test che soddisfa e quindi al prodotto di lavoro originale.