Concetto: Stub
Questa guida descrive come creare uno stub che può essere utilizzato come segnaposto per dei componenti che devono essere ancora sviluppati. Uno stub è un componente che non fa altro che dichiarare sé stesso ed i parametri che accetta. Lo stub contiene codice a sufficienza per poter essere compilato e collegato al resto dei componenti.
Relazioni
Elementi correlati
Descrizione principale

Introduzione

Un componente viene testato inviando degli input alla sua interfaccia, attendendo che il componente li elabori ed infine controllandone i risultati. Nel corso dell'elaborazione un componente probabilmente utilizza altri componenti, inviando loro degli input ed utilizzandone i risultati:

Diagramma descritto nel testo di accompagnamento.

Fig 1: Test di un componente implementato

Gli altri componenti potrebbero causare dei problemi al test:

  1. Potrebbero non essere stati ancora implementati.
  2. Potrebbero contenere dei difetti che impediscono il funzionamento dei test o che potrebbero far impiegare molto tempo nella ricerca dell'errore per poi scoprire che non era causato dal proprio componente.
  3. Possono rendere difficile l'esecuzione dei test, quando sono necessari. Se un componente è un database commerciale, l'azienda potrebbe non disporre di licenze a sufficienza per tutti. Oppure uno dei componenti potrebbe essere dell'hardware disponibile solo ad orari prestabiliti in un altro laboratorio.
  4. Possono rendere la fase di test talmente lenta da impedire l'esecuzione dei test con la frequenza necessaria. Ad esempio, l'inizializzazione del database potrebbe richiedere cinque minuti a test.
  5. Potrebbe essere difficile indurre i componenti a produrre determinati risultati. Ad esempio, si potrebbe volere che ogni metodo che scrive su disco sia in grado di gestire gli errori di disco pieno. Come si può essere sicuri che il disco si riempia nel momento esatto in cui viene richiamato quel metodo?

Per evitare questi problemi è possibile scegliere di utilizzare i componenti stub (detti anche oggetti fittizi). I componenti stub agiscono come dei veri componenti, almeno per quanto riguarda i valori che il componente in fase di test invia loro mentre esegue le sue verifiche. Possono anche andare oltre: possono essere degli emulatori a scopo generico che tentano di emulare fedelmente la maggior parte o la totalità delle funzionalità del componente. Ad esempio, spesso è una buona strategia creare degli emulatori software per l'hardware. Hanno la stessa funzionalità dell'hardware, solo più lenta. Sono utili perché supportano meglio il debug, ne sono disponibili più copie e possono essere utilizzati prima che sia terminato l'hardware.

Diagramma descritto nel testo di accompagnamento.

Fig2: Verifica di un componente implementato rendendo stub un componente da cui dipende

Gli stub hanno due svantaggi.

  1. Possono essere costosi da creare. Questo è vero specialmente nel caso degli emulatori. Poiché sono software, anch'essi necessitano di manutenzione.
  2. Mascherano gli errori. Ad esempio, supponiamo che il componente utilizzi delle funzioni trigonometriche ma non è ancora disponibile una libreria. I tre scenari di test richiedono il seno di tre angoli: 10 gradi, 45 gradi e 90 gradi. Con la calcolatrice si individuano i valori corretti e si costruisce uno stub per il seno che restituisce rispettivamente 0,173648178, 0,707106781 e 1.0. Funziona tutto correttamente finché non si integra il proprio componente con la vera libreria trigonometrica, la cui funzione di seno accetta parametri espressi come radianti e quindi restituisce -0,544021111, 0,850903525 e 0,893996664. Si tratta di un difetto nel codice che viene rilevato in seguito, e con più impegno di quanto non si desideri.

Stub e pratiche di progettazione software

A meno che gli stub non siano stati creati perché il vero componente non era ancora disponibile, si deve prevedere di conservarli dopo lo sviluppo. I test che supportano probabilmente saranno importanti durante la manutenzione. Gli stub, quindi, devono essere scritti con standard più elevati di quelli del codice da scarto. Anche se non devono rispettare gli standard del codice del prodotto - ad esempio, la maggior parte non ha bisogno di una suite di test propria - in seguito gli sviluppatori dovranno gestirli come componenti della modifica del prodotto. Se la manutenzione è tropo difficile, gli stub verranno eliminati e il relativo investimento andrà perso.

Specialmente quando devono essere conservati, gli stub alterano la progettazione del componente. Ad esempio, si supponga che il componente utilizzi un database per memorizzare permanentemente le coppie chiave/valore. Considerare due scenari di progettazione:

Scenario 1: Il database viene utilizzato per la verifica oltre che per un uso normale. L'esistenza del database non deve essere nascosto dal componente. È possibile inizializzarlo con il nome del database:

 public Component(

String databaseURL) { try {     databaseConnection =         DriverManager.getConnection(databaseURL);     ... } catch (SQLException e) {...}     }

E, sebbene non si desideri che ogni posizione che ha letto o scritto un valore costruisca un'istruzione SQL, certamente si avranno dei metodi che contengono SQL. Ad esempio, il codice del componente che ha bisogno di un valore chiama questo metodo di componente:
 public String get(String key) { try {     Statement stmt =       databaseConnection.createStatement();     ResultSet rs = stmt.executeQuery(       

"SELECT value FROM Table1 WHERE key=" + key);     ... } catch (SQLException e) {...}     }

Scenario 2: Per la verifica, il database viene sostituito da uno stub. Il codice del componente dovrebbe essere lo stesso a prescindere che sia in esecuzione sul vero database o sullo stub. Quindi deve essere codificato per utilizzare i metodi di un'interfaccia astratta:
 

interface KeyValuePairs { String 

get(String key); void 

put(String key, String value);     }

I test implementerebberot KeyValuePairs con qualcosa di semplice come una tabella di hash:
 

class FakeDatabase implementa KeyValuePairs  { Hashtable table = new Hashtable(); public String 

get(String key) {     return (String) table.get(key); } public void 

put(String key, String value) {     table.put(key, value); }     }
Quando non viene testato, il componente utilizza un oggetto adattatore che converte le chiamate all'interfaccia KeyValuePairs in istruzioni SQL:
 

class DatabaseAdapter implementa KeyValuePairs { private Connection databaseConnection; public DatabaseAdapter(String databaseURL) {     try {         databaseConnection =             DriverManager.getConnection(databaseURL);         ...     } catch (SQLException e) {...} } public String 

get(String key) {     try {         Statement stmt =            databaseConnection.createStatement();         ResultSet rs = stmt.executeQuery(           "SELECT value FROM Table1 WHERE key=" + key);         ...     } catch (SQLException e) {...} } public void 

put(String key, String value) {     ... }     }

Il componente potrebbe avere un singolo costruttore per entrambi i test ed altri client. Il costruttore prenderebbe un oggetto che implementa KeyValuePairs. Oppure potrebbe fornire quell'interfaccia solo per i test, richiedendo che i client ordinari del componente passino il nome di un database:
 class Component { 

public Component(String databaseURL) {     this.valueStash = new DatabaseAdapter(databaseURL); } // For testing. 

protetto Component(KeyValuePairs valueStash) {     this.valueStash = valueStash; }     }

Quindi, dal punto di vista dei programmatori client, i due scenari di progettazione producono la stessa API, ma una è testabile più prontamente. (Alcuni test possono utilizzare il vero database ed altri quello stub).

Ulteriori informazioni


Per ulteriori informazioni correlate agli stub, consultare quanto segue: