SeamFramework.orgCommunity Documentation

Capitolo 37. Test delle applicazioni Seam

37.1. Test d'unità dei componenti Seam
37.2. Test d'integrazione dei componenti Seam
37.2.1. Uso dei mock nei test d'intergrazione
37.3. Test d'integrazione delle interazioni utente in applicazioni Seam
37.3.1. Configurazione
37.3.2. Uso di SeamTest con un altro framework di test
37.3.3. Test d'integrazione con Dati Mock
37.3.4. Test d'integrazione di Seam Mail

La maggior parte delle applicazioni Seam ha bisogno di almeno due tipi di test automatici: test di unità per testare un particolare componente Seam in isolamento, e test d'integrazione per provare tutti i layer java dell'applicazione (cioè tutto, tranne le pagine di vista).

Entrambi i tipi di test sono facili da scrivere.

Tutti i componenti Seam sono POJO. Questo è un buon punto per partire se si vogliono eseguire dei facili test di unità. E poiché Seam enfatizza l'uso della bijection per le interazioni tra componenti e l'accesso ad oggetti contestuali, è molto facile testare un componente Seam fuori dal suo normale ambiente di runtime.

Si consideri il seguente componente Seam che crea una dichiarazione di account per un cliente:

@Stateless

@Scope(EVENT)
@Name("statementOfAccount")
public class StatementOfAccount {
   
   @In(create=true) EntityManager entityManager
   
   private double statementTotal;
   
   @In
   private Customer customer;
   
   @Create
   public void create() {
      List<Invoice
> invoices = entityManager
         .createQuery("select invoice from Invoice invoice where invoice.customer = :customer")
         .setParameter("customer", customer)
         .getResultList();
      statementTotal = calculateTotal(invoices);
   }
   
   public double calculateTotal(List<Invoice
> invoices) {
      double total = 0.0;
      for (Invoice invoice: invoices)
      {
         double += invoice.getTotal();
      }
      return total;
   }
   
   // getter and setter for statementTotal
   
}

Si può scrivere un test d'unità per il metodo calculateTotal (che testa la business logic del componente) come segue:

public class StatementOfAccountTest {

    
    @Test
    public testCalculateTotal {
       List<Invoice
> invoices = generateTestInvoices(); // A test data generator
       double statementTotal = new StatementOfAccount().calculateTotal(invoices);
       assert statementTotal = 123.45;
    }   
}

Si vede che non si sta testando il recupero dei dati e la persistenza dei dati da/a database; e neppure si testa alcuna funzionalità fornita da Seam. Si sta solamente testanto la logica del POJO. I componenti Seam solitamente non dipendono direttamente dall'infrastruttura del container, e quindi la maggior parte dei test d'unità sono facili come quello mostrato!

Comunque se si vuole testare l'intera applicazione, si consiglia di continuare nella lettura.

Il test d'integrazione è leggermente più difficile. In questo caso non si può eliminare l'infrastruttura del container che invece fa parte di ciò che va testato! Allo stesso tempo non si vuole essere forzati ad eseguire un deploy dell'applicazione in un application server per fare girare i test. Occorre essere in grado di riprodurre l'infrastruttura essenziale del container dentro l'ambiente di test per poter provare l'intera applicazione senza nuocere troppo alle performance.

L'approccio adottato da Seam è quello di consentire di scrivere test che possano provare i componenti mentre girano dentro un ambiente di container ridotto (Seam assieme al container JBoss Embedded; vedere Sezione 30.6.1, «Installare JBoss Embedded» per i dettagli di configurazione)

public class RegisterTest extends SeamTest

{
   
   @Test
   public void testRegisterComponent() throws Exception
   {
            
      new ComponentTest() {
         protected void testComponents() throws Exception
         {
            setValue("#{user.username}", "1ovthafew");
            setValue("#{user.name}", "Gavin King");
            setValue("#{user.password}", "secret");
            assert invokeMethod("#{register.register}").equals("success");
            assert getValue("#{user.username}").equals("1ovthafew");
            assert getValue("#{user.name}").equals("Gavin King");
            assert getValue("#{user.password}").equals("secret");
         }
         
      }.run();
      
   }
   ...
   
}

Un problema ancora più difficile è quello di emulare le interazioni utente. Un terzo problema si verifica quando vengono messe le asserzioni. Alcuni framewrok di test consentono di testare un'intera applicazione riproducendo le interazioni utente con il browser. Questi framework hanno un loro spazio d'uso, ma non sono adatti per l'uso in fase di sviluppo.

SeamTest consente di scrivere test sotto forma di script (scripted), in un ambiente JSF simulato. Il ruolo di un test scripted è quello di riprodurre l'interazione tra la vista ed i componenti Seam. In altre parole si pretende di essere l'implementazione JSF!

Questo approccio testa ogni cosa tranne la vista.

Si consideri una vista JSP per il componente testato come unità visto sopra:


<html>
 <head>
  <title
>Register New User</title>
 </head>
 <body>
  <f:view>
   <h:form>
     <table border="0">
       <tr>
         <td
>Username</td>
         <td
><h:inputText value="#{user.username}"/></td>
       </tr>
       <tr>
         <td
>Real Name</td>
         <td
><h:inputText value="#{user.name}"/></td>
       </tr>
       <tr>
         <td
>Password</td>
         <td
><h:inputSecret value="#{user.password}"/></td>
       </tr>
     </table>
     <h:messages/>
     <h:commandButton type="submit" value="Register" action="#{register.register}"/>
   </h:form>
  </f:view>
 </body>
</html
>

Si vuole testare la funzionalità di registrazione dell'applicazione (la cosa che succede quando l'utente clicca il pulsante Registra). Si riprodurrà il ciclo di vita della richiesta JSF in un test TestNG automatizzato:

public class RegisterTest extends SeamTest

{
   
   @Test
   public void testRegister() throws Exception
   {
            
      new FacesRequest() {
         @Override
         protected void processValidations() throws Exception
         {
            validateValue("#{user.username}", "1ovthafew");
            validateValue("#{user.name}", "Gavin King");
            validateValue("#{user.password}", "secret");
            assert !isValidationFailure();
         }
         
         @Override
         protected void updateModelValues() throws Exception
         {
            setValue("#{user.username}", "1ovthafew");
            setValue("#{user.name}", "Gavin King");
            setValue("#{user.password}", "secret");
         }
         @Override
         protected void invokeApplication()
         {
            assert invokeMethod("#{register.register}").equals("success");
         }
         @Override
         protected void renderResponse()
         {
            assert getValue("#{user.username}").equals("1ovthafew");
            assert getValue("#{user.name}").equals("Gavin King");
            assert getValue("#{user.password}").equals("secret");
         }
         
      }.run();
      
   }
   ...
   
}

Si noti che si è esteso SeamTest, il quale fornisce un ambiente Seam ai componenti, e si è scritto uno script di test come classe anonima che estende SeamTest.FacesRequest, la quale fornisce un ciclo di vita emulato della richiesta JSF. (C'è anche SeamTest.NonFacesRequest per testare le richieste GET). Si è scritto codice in metodi che vengono chiamati in varie fasi JSF, per emulare le chiamate che JSF farebbe ai componenti. Infine sono state scritte le asserzioni.

Nelle applicazioni d'esempio di Seam ci sono molti test d'integrazione che mostrano casi ancora più complicati. Ci sono istruzioni per eseguire questi test usando Ant o usando il plugin TestNG per Eclipse:

Se è stato usato seam-gen per creare il progetto si è già pronti per scrivere test. Altrimenti occorre configurare l'ambiente di test all'interno del proprio tool di build (es. ant, maven, eclipse).

Prima si guardi alle dipendenze necessarie:


E' molto importante non inserire nel classpath le dipendenze di JBoss AS a compile time da lib/ (es. jboss-system.jar), altrimenti questo causerà il non avvio di JBoss Embedded. Quindi si aggiungano solo le dipendenze necessarie per partire (es. Drools, jBPM).

Occorre includere nel classpath la directory bootstrap/ che contiene la configurazione per JBoss Embedded.

E sicuramente occorre mettere nel classpath il progetto ed i test così come i jar del framework di test. Non si dimentichi di mettere nel classpath anche tutti i file di configurazione corretti per JPA e Seam. Seam chiede a JBoss Embedded di deployare le risorse (jar o directory) che hanno seam.properties nella root. Quindi se non si assembla una struttura di directory che assomiglia ad un archivio deployabile contenente il progetto, occorre mettere seam.properties in ciascuna risorsa.

Di default un progetto generato userà per i test java:/DefaultDS (un datasource predefinito HSQL in JBoss Embedded). Se si vuole usare un altro datasource, si metta foo-ds.xml nella directory bootstrap/deploy.

If you need to insert or clean data in your database before each test you can use Seam's integration with DBUnit. To do this, extend DBUnitSeamTest rather than SeamTest.

Devi fornire un dataset per DBUnit.


<dataset>
   
   <ARTIST 
      id="1"
      dtype="Band"
      name="Pink Floyd" />
      
   <DISC
      id="1"
      name="Dark Side of the Moon"
      artist_id="1" />
      
</dataset
>

e comunicarlo a Seam facendo l'override di prepareDBUnitOperations():"

protected void prepareDBUnitOperations() {

    beforeTestOperations.add(
       new DataSetOperation("my/datasets/BaseData.xml")
    );
 }

DataSetOperation è impostato di default a DatabaseOperation.CLEAN_INSERT se non viene specificata qualche altra operazione come argomento del costruttore. L'esempio di cui sopra pulisce tutte le tabelle definite in BaseData.xml e quindi inserisce tutte le righe dichiarate in BaseData.xml prima che venga invocato ogni metodo @Test.

Se viene richiesta un'ulteriore pulizia prima dell'esecuzione di un metodo di test, si aggiungano operazioni alla lista afterTestOperations.

Occorre informare DBUnit del datasource usato impostando il parametro TestNG chiamato datasourceJndiName:


<parameter name="datasourceJndiName" value="java:/seamdiscsDatasource"/>
         

DBUnitSeamTest supporta MySQL e HSQL- occorre dire quale database viene usato:

<parameter name="database" value="HSQL" />

Questo consente anche di inserire dati binari nel set dei dati di test (N.B. non è stato testato sotto Windows). Occorre dire dove si trovano queste risorse:

<parameter name="binaryDir" value="images/" />

Si devono specificare questi tre parametri in testng.xml.

Per usare DBUnitSeamTest con altri database occorre implementare alcuni metodi. Si legga javadoc di AbstractDBUnitSeamTest per saperne di più.

E' facilissimo eseguire il test d'integrazione con Seam Mail:

public class MailTest extends SeamTest {

    
   @Test
   public void testSimpleMessage() throws Exception {
        
      new FacesRequest() {
         @Override
         protected void updateModelValues() throws Exception {
            setValue("#{person.firstname}", "Pete");
            setValue("#{person.lastname}", "Muir");
            setValue("#{person.address}", "test@example.com");
         }
            
         @Override
         protected void invokeApplication() throws Exception {
            MimeMessage renderedMessage = getRenderedMailMessage("/simple.xhtml");
            assert renderedMessage.getAllRecipients().length == 1;
            InternetAddress to = (InternetAddress) renderedMessage.getAllRecipients()[0];
            assert to.getAddress().equals("test@example.com");
         }
            
      }.run();       
   }
}

Viene creata una nuova FacesRequest come normale. Dentro la sezione invokeApplication mostriamo il messaggio usando getRenderedMailMessage(viewId);, passando il viewId del messaggio da generare. Il metodo restituisce il messaggio sul quale è possibile fare i test. Si può anche usare ogni altro metodo standard del ciclo di vita JSF.

Non c'è alcun supporto per il rendering dei componenti JSF standard, così non è possibile testare facilmente il corpo dei messaggi email.