MokaByte 64 - Giugno 2002 
Con il test in testa
II parte
di
Sandro Pedrazzini
In questa seconda parte dell'articolo iniziato il mese scorso, continuiamo con l'esempio di utilizzo di JUnit, introducendo il controllo delle eccezioni. In seguito introdurremo brevemente due tools per il test di siti Web ottenibili gratuitamente: HttpUnit [1] e Canoo WebTest [2].

JUnit
Continuiamo l'esempio iniziato con l'articolo del mese scorso [3].
Si trattava volutamente di un esempio numerico, per mostrare che anche in casi come questi, spesso utilizzati nei corsi di informatica di base, l'automazione del test può essere esercitata e rappresenta un valore aggiunto.
Riprendo la descrizione del problema.
Si trattava di definire in una classe, un metodo (chiamato poi modifica()), che ricevuto un intero in input restituisse la metà del suo argomento se questi è pari o il triplo più uno se invece è dispari.
Il metodo andava in seguito usato per stabilire (altro metodo, chiamato sequenzaSingola()) quanto è lunga la sequenza di applicazioni ripetute della funzione fino ad ottenere 1 per un valore qualsiasi positivo maggiore di 2 (valore: variabile di istanza inizializzata dal costruttore della classe). Per applicazione ripetuta si intende che il valore restituito da una chiamata viene passato come argomento ad una chiamata successiva.
La lunghezza della sequenza trovata è da registrare nell'oggetto istanziato, da cui, inoltre, deve sempre essere possibile, attraverso un metodo, richiamarne il valore (int getLength()) e il numero che ha generato una sequenza di questa lunghezza (int getValue()).

Esempio di sequenza:

valore iniziale: 10
lunghezza della sequenza: 6 (10 5 16 8 4 2)


La funzione sequenzaSingola() così come implementata e verificata nell'articolo precedente, non controlla il dominio del valore di cui deve calcolare la lunghezza della sequenza.

static int sequenzaSingola(int start){
  int val = start, count = 0;
  while(val>1){
    val=modifica(val);
    count++;
  }
  return count;
}


Eccezioni
Se torniamo alla definizione del problema, ci accorgiamo che il valore di cui cerchiamo la lunghezza della sequenza dev'essere positivo e maggiore di 1.
Questo significa che nell'implementazione della soluzione dobbiamo prevedere una verifica e un'azione da eseguire nel caso in cui il controllo desse risultato negativo. L'azione scelta in questo caso è il lancio di un'eccezione.

static int sequenzaSingola(int start) throws Exception{
  if (start<2)
    throw new Exception("Valore fuori dominio");
  int val = start, count = 0;
  while(val>1){
    val=modifica(val);
    count++;
    }
  return count;
}

A questo punto prima adattiamo il metodo di test esistente, poi ne aggiungiamo un secondo:

public void testSequenzaSingola(){
  try{
    assertTrue(Sequenza.sequenzaSingola(10) == 6);
    assertTrue(Sequenza.sequenzaSingola(12) == 9);
  }
  catch(Exception e){
    assertTrue(false);
  }
}

public void testSequenzaSingolaNegativa(){
  try{
    Sequenza.sequenzaSingola(-2);
    assertTrue(false);
  }
  catch(Exception e){
    assertTrue(true);
  }
}


Quest'ultimo esempio mostra come gestire le eccezioni all'interno di metodi di test in JUnit. Il primo caso prevede un assert negativo all'interno del blocco catch, dove il controllo del programma non dovrebbe mai entrare, mentre il secondo prevede un assert negativo come istruzione che segue la chiamata che dovrebbe lanciare l'eccezione. Questo ci assicura il controllo che il metodo non termina regolarmente.

 

Ultimi elementi
Introduciamo ora gli ultimi elementi di soluzione del problema, osservando la classe intera:

public class Sequenza{
  int valore, contatore;

  public Sequenza(int v){
    valore = v;
    contatore = 0;
  }

  static int modifica(int val){
    ...
  }

  static int sequenzaSingola(int start) throws Exception{
    ...
  }

  public int getValue(){
    return valore;
  }

  public int getLength() throws Exception{
    if (contatore == 0){
      contatore = sequenzaSingola(valore);
      return contatore;
    }
    else
      return contatore;
  }


Ed ecco il metodo che verifica getValue() e getLength():

public void testLength(){
  Sequenza seq = new Sequenza(10);
  assertTrue(seq!=null);
  assertTrue(seq.getValue() == 10);
  try{
    assertTrue(seq.getLength() == 6);
  }
  catch(Exception e){
    assertTrue(false);
  }
}


Web Test
Un tipo di programma che necessita senz'altro di test è quello rappresentato dalle applicazioni Web. Non tanto test di unità (questi andrebbero integrati nel codice a livello server, ad esempio con JUnit se il server utilizza Java), ma piuttosto test di funzionalità, che permettano di scoprire se un link non esiste più, se la risposta ad una form è diversa oggi da ieri, ecc.
Questi test sono necessari per le caratteristiche intrinseche di queste applicazioni, che cambiano velocemente nel tempo, sia in aspetto che in funzionalità.
Il metodo più diretto per verificare la funzionalità di siti Web è quello di verificare il comportamento del client, sottoposto a sessioni di interazione con il server. Anche in questo caso esistono prodotti disponibili in rete. Vediamone due a tale scopo, entrambi con licenza open source, HttpUnit (http://httpunit.sourceforge.net) e Canoo WebTest (http://webtest.canoo.com), mostrando due semplici esempi di utilizzo.

 

HttpUnit
Malgrado il nome tragga in inganno, si tratta di una API di classi Java per test funzionali, non test di unità. Viene utilizzato per verificare nel tempo la consistenza di siti Web.
La classe principale è WebConversation, che rappresenta un browser che conversa con un server. Attraverso questa classe è possibile costruire una serie di interazioni con il server.
Ecco l'esempio mostrato nel cookbook:

WebConversation wc = new WebConversation();
WebResponse resp =
wc.getResponse("http://httpunit.sourceforge.net/doc/Cookbook.html");
WebLink link = resp.getLinkWith("response"); WebRequest req = link.getRequest(); WebResponse jdoc = wc.getResponse(req);

L'oggetto di tipo WebResponse rappresenta la pagina ottenuta all'indirizzo specificato. All'interno di questa pagina sono possibili varie operazioni come getText(), getTitle(), getTables(), getLinks(), ecc. Tutte queste operazioni si riferiscono al testo ottenuto.
Nell'esempio specifico si cerca il link che corrisponda al testo "response". Il link viene rappresentato dall'oggetto WebLink, dal quale è poi possibile partire con ulteriori richieste, come appunto getRequest(), utilizzata nell'esempio.

 

Canoo WebTest
Canoo WebTest (http://webtest.canoo.com) è simile a HttpUnit come funzionalità di base, ma compie un passo ulteriore permettendo di specificare le sequenze di test in XML.
Può sembrare una differenza irrilevante, anzi inutile, per programmatori Java. In realtà questo livello in più è quello che permette anche a non sviluppatori Java di creare test e soprattutto di aggiornarli. Infatti la differenza tra test di unità e test funzionali consiste anche in questo, cioè nel fatto che i test funzionali possono essere creati anche da persone non direttmente coinvolte nello sviluppo.
Chi partecipa alla mailing list comunitaria di Canoo WebTest riporta che in alcuni casi questo tool è stato utilizzato in un primo passaggio come definizione di requisiti, per poi, senza modifiche, passare a test vero e proprio.
Il motore di Canoo WebTest è Ant [4], un prodotto del progetto Jakarta [5] di Apache.
Ecco l'esempio di utilizzo riportato nel whitepaper:

<target name="login">
<testSpec name="normal" >
<config
host="www.xyz.com"
port="80"
protocol="http"/>
<steps>
<invoke stepid="get Login Page" url="login.jsp"/>
<verifytitle stepid="we should see the login title"
text="Login Page" />
<setinputfield stepid="set user name"
name="username"
value="scott" />
<setinputfield stepid="set password"
name="password"
value="tiger" />
<clickbutton stepid="Click the submit button"
label="let me in" />
<verifytitle stepid="Home Page follows if login ok"
text="Home Page" />
</steps>
</testSpec>
</target>

Ogni specificazione di test consiste in una configurazione (<config>, che è anche possibile specificare in un file separato condivisibile tra più test) e una sequenza di passi, che simula il comportamento di un potenziale client.
Come si vede si possono verificare elementi all'interno della pagina chiamata, attivare pulsanti, riempire campi di testo, ecc.
Una cosa importante da notare è che Canoo WebTest, come del resto HttpUnit, si basa sull'interazione del client con il server: è perciò indipendente dal linguaggio usato per l'applicazione server. Può quindi essere applicato a qualsiasi sito Web.
Si tratta di un prodotto open source alla cui estensione hanno contribuito già in parecchi e tutti possono continuare a farlo. La cosa è resa semplice dal fatto che ogni nuovo tag XML può essere implementato in modo completamente indipendente dal resto dell'applicazione.

 

Conclusioni
In questi due articoli abbiamo cercato di mostrare come il test automatico (sia di unità che funzionale) debba essere considerato parte integrante della progettazione e dello sviluppo di applicazioni. Questo non viene ancora insegnato in modo sistematico, ma i tools ottenibili sulla rete stanno contribuendo sempre più a diffondere questa attività anche tra i programmatori Java.

 

Bibliografia
[1] HttpUnit: http://httpunit.sourceforge.net
[2] Canoo WebTest: http://webtest.canoo.com
[3] Pedrazzini Sandro: Con il test in testa: Parte I, Moka Byte, http://www.mokabyte.com, Maggio 2002.
[4] Ant, the leading build automation tool: http://jakarta.apache.org/ant
[5] The Jakarta Project: http://jakarta.apache.org

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it