Progettazione, sviluppo e gestione del change con modelli UML, framework e strumenti open source

II parte: La codifica della logica applicativadi

In questa seconda parte cominciamo a illustrare dei progetti di esempio che realizzano la modellazione presentata in precedenza. Ci occupiamo di vedere come viene implementata la logica applicativa, utilizzando dei framework (Ant, JUnit) che consentono di attuare il nostro modello di sviluppo.

Introduzione

In questa parte iniziamo ad illustrare i progetti di sviluppo che realizzano la modellazione UML presentata nell'articolo precedente. In particolare, la descrizione del workspace verrà fatta presentando i progetti che implementano la logica applicativa e sarà completata nel prossimo articolo con i progetti strettamente legati ai middleware di esecuzione e alle tecnologie di pubblicazione. I listati presentati sono disponibili nell'allegato, scaricabile dal menù a destra.

Oltre a realizzare la modellazione UML il workspace sarà integrato da alcuni progetti e implementazioni di framework (Ant, JUnit) non dichiarati nella modellazione, ma che, come avremo modo di illustrare, concorreranno a rendere attuabile un modello di sviluppo in cui l'effort totale viene scomposto in attività eseguibili in parallelo.

Organizzazione e contenuto del workspace di sviluppo

Nel workspace di sviluppo sono quindi presenti i progetti che definiscono l'interfaccia e l'implementazione del componente, i progetti con la codifica JUnit dei casi di test e i progetti Ant di compilazione dei sorgenti, di packaging delle librerie e di esecuzione dei JUnit. Per quello che riguarda il progetto che implementa la logica applicativa, i progettisti oltre a quello di implementazione effettiva, ne pubblicano uno contenente le corrispondenti classi skeleton. È vero infatti che in un contesto caratterizzato dalla parallelizzazione delle attività di sviluppo sia verosimile che tutte le implementazioni dei moduli di dipendenza non saranno subito disponibili. Senza aspettarne il rilascio definitivo, i gruppi di lavoro potranno, se necessario, iniziare lo sviluppo realizzandone, a partire dalle classi skeleton, delle implementazioni dedicate al loro test unitario. Per rendere la logica applicativa aderente ai requisiti funzionali, queste implementazioni personalizzate vengono quindi validate attraverso i casi di test progettati in sede di modellazione e realizzati attraverso JUnit. Questo permetterà di non vanificare l'efficienza di questa strategia di sviluppo durante la fase di test di integrazione, quando saranno disponibili tutte le implementazioni effettive dei componenti.

Progetto con le librerie comuni

Iniziamo la descrizione del workspace dal progetto che raccoglie le librerie dei framework e dei run time dei middleware. Nella parte dedicata al piano della configurazione vedremo che queste librerie, nonostante la caratteristica di essere di fuori del perimetro di implementazione del progetto, verranno pubblicate sullo strumento di gestione del versionamento in ragione del fatto che ogni sviluppatore sia sempre nelle condizioni di poter utilizzare quelle ufficiali.

In questo progetto vengono quindi pubblicate le librerie dichiarate dai progettisti nel diagramma dei componenti che specializza le dipendenze (viste nella I parte, fig. 7). Il componente infatti dipende dalla JRE, dalle librerie di run time dell'application server e dal framework di Spring. Queste ultime due saranno utilizzate dai progetti presentati nella III parte della serie, quando verranno descritti i progetti legati ai middleware di esecuzione e alle tecnologie di esposizione della logica applicativa.

Le librerie dei framework Ant e JUnit rappresentano ulteriori dipendenze dei progetti presenti nel workspace ma che non contengono logica applicativa. Si tratta dei progetti di compilazione automatica dei sorgenti, di esportazione delle librerie e di esecuzione dei JUnit.

 

 

Figura 1 - Progetto con le librerie comuni.

 

Progetti di implementazione dei DTO

In questo progetto Java vengono implementati i DTO e le classi di enumeration dell'applicazione. Abbiamo concentrato in questo progetto e in unico namespace tutte le classi DTO solo per semplificare la lettura e il workspace di esempio.

 

 

Figura 2 - Progetto con i DTO e le interfacce di enumeration.

 

Chiaramente in uno scenario reale è necessario organizzare le classi con la giusta granularità, articolando il packaging in relazione ai componenti applicativi. Una libreria unica, infatti, che concentri tutte le classi che serializzano gli oggetti, sarebbe estremamente difficile da gestire sia dal punto di vista della distribuzione che per la concorrenza delle implementazioni.

 

 

Figura 3 - Progetto con il task di build e creazione package per i DTO e le interfacce di enumeration.

 

Riportiamo di seguito il progetto con i sorgenti, completato da quello Ant di compilazione ed esportazione della libreria corrispondente.



       
             APPLICAZIONE_DTO: compilazione sorgenti e esportazione della libreria
       
       
                                  ../APPLICAZIONE_DTO/it/luigibennardis/applicazione/iface"
              destdir="../APPLICAZIONE_DTO/build"
              />
       
      
                    depends="buildSRC"
             description="Esporta il jar con le classi di interfaccia compilate">
             
                                  basedir="../APPLICAZIONE_DTO/build" />

Progetti di implementazione della logica applicativa

Il pattern di sviluppo indicato dalla modellazione UML viene risolto attraverso la definizione di due progetti separati che implementano uno l'interfaccia e l'altro la logica applicativa. Quest'ultima viene codificata attraverso una classe Java standard (POJO) senza nessun riferimento alla tecnologia di pubblicazione così come modellato dal diagramma di struttura composita (I parte, fig. 8) e dal diagramma delle classi (I parte, fig. 11).

Come anticipato, il workspace viene completato dai progetti Ant di compilazione ed esportazione delle librerie, dal progetto di implementazione dei casi di test e da quello necessario all'esecuzione automatica di questi ultimi.

Interfaccia del servizio.

Qui di seguito viene riportato il progetto con l'interfaccia e la corrispondente classe di implementazione, le cui dipendenze sono verso il run time di Java e la libreria dei DTO.

 

 

Figura 4 - Progetto con l'interfaccia del componente.

 

package it.luigibennardis.servizio01.iface;
 
import java.util.Date;
 
import it.luigibennardis.applicazione.dto.Cliente;
import it.luigibennardis.applicazione.dto.Ordine;
 
public interface IServizio01 {
       public Ordine[] listaOrdiniCliente(String codCliente);
       public Ordine dettaglioOrdineClienteData(String codCliente, Date data);
       public Cliente dettaglioCliente(String codCliente);
}

Skeleton di implementazione della logica applicativa.

Questo progetto contiene le classi Java con gli skeleton di implementazione della logica applicativa. Riportiamo di seguito il progetto Java e la corrispondente classe di implementazione assieme al progetto Ant di compilazione dei sorgenti e di esportazione dei compilati come libreria. Anche in questo caso le dipendenze sono verso le classi contenute nel run time di Java e verso la libreria dei DTO.

 

 

Figura 5 - Progetto con l'implementazione del componente.

 

package it.luigibennardis.libreria01.impl;
 
import java.util.Date;
 
import it.luigibennardis.applicazione.dto.Cliente;
import it.luigibennardis.applicazione.dto.Ordine;
import it.luigibennardis.applicazione.iface.ITipoPagamento;
import it.luigibennardis.servizio01.iface.IServizio01;
 
public class Servizio01 implements IServizio01{
 
       public Cliente dettaglioCliente(String codCliente) {
             return new Cliente(codCliente,"Rossi","Mario","Via Dei Castani 5 ROMA");
            
       }
      
       public Ordine dettaglioOrdineClienteData(String codCliente, Date data) {
             return new Ordine("O0001",codCliente,ITipoPagamento.BONIFICO_BANCARIO,1000.88);
       }
      
       public Ordine[] listaOrdiniCliente(String codCliente) {
             Ordine[] ordini = new Ordine[3];
             ordini[0].setCodCliente(codCliente);
             ordini[0].setCodOrdine("O00001");
             ordini[0].setImportoOrdine(10000.88);
             ordini[0].setTipoPagamento(ITipoPagamento.BONIFICO_BANCARIO);
            
             ordini[1].setCodCliente(codCliente);
             ordini[1].setCodOrdine("O00002");
             ordini[1].setImportoOrdine(23000.88);
             ordini[1].setTipoPagamento(ITipoPagamento.BONIFICO_BANCARIO);
            
             ordini[2].setCodCliente(codCliente);
             ordini[2].setCodOrdine("O00003");
             ordini[2].setImportoOrdine(45600.85);
             ordini[2].setTipoPagamento(ITipoPagamento.CARTA_CREDITO);
                   
             return ordini;
       }
 
}

 

 




       
             Compila i sorgenti di implementazione del servizio ed esporta la libreria
       
      
                    description="Compila le classi che implementa il servizio ">
                     
                                                 destdir="../SERVIZIO01_IMPL/build"
                            classpath="../SERVIZIO01_EXPORT_EAR/EXPORT/SERVIZIO01_IFACE.jar:
                           ../APPLICAZIONE_DTO_BUILD/EXPORT/APPLICAZIONE_DTO.jar" />
       
      
                    description="Esporta il jar con le classi di implementazione compilate">
                    
                                         basedir="../SERVIZIO01_IMPL/build" />

Implementazione dei casi di test

In questo progetto vengono codificati, attraverso il framework JUnit, i casi di test previsti dalla modellazione UML. I test case così implementati possono essere raggruppati in suite di test specializzate, di tipo, ad esempio, funzionale o di non regressione. Si possono quindi definire molteplici scenari applicabili per un contesto di applicazione flessibile.

 

 

Figura 6 - Progetto con i JUnit del componente.

 

Riportiamo di seguito un esempio di una classe JUnit che implementa un caso di test. Possiamo distinguere le istruzioni che inizializzano l'ambiente (@Before) e che lo rilasciano (@After) da quelle che codificano il test case (@Test). In questo modo vengono separate le istruzioni strettamente legate all'esecuzione del caso di test da quelle necessarie a inizializzare l'ambiente di esecuzione, necessarie, queste ultime, all'esecuzione del software al di fuori del contesto applicativo per cui viene realizzato. Chiaramente nel corpo del metodo che implementa il test riportiamo istruzioni di esempio.

public class Servizio01Test extends TestCase{
       @Before
       public void setUp() throws Exception {
              super.setUp();
             System.out.println("-------------------");
              System.out.println(this.getClass().getSimpleName());
              System.out.println("@Before: completamento inizializzazione ambiente.");
       }
 
       @Test
       public void testMethod()  {
             Servizio01  servizio = new Servizio01();
             String codCliente = "CLI0001";
             Cliente testCli = new Cliente(codCliente,"Bennardis","Luigi",
                                               "Salita San Sebastianello 1, Roma");
             assertTrue(testCli.toXML().
                    equals(servizio.dettaglioCliente(codCliente).toXML()));
       }
 
       @After
       public void tearDown() throws Exception {
              super.tearDown();
             System.out.println("@After: completamento rilascio ambiente.");
              System.out.println("-------------------");
                
       }
}

Riportiamo di seguito una classe di esempio che definisce una suite integrando due test case.

package it.luigibennardis.libreria01.impl.test;
import junit.framework.TestSuite;
import it.luigibennardis.libreria01.impl.test.Servizio01TestB;
public class AllLibreria01Test extends TestSuite{
       public AllLibreria01Test(String name) {
              super(name);
       }
       public static TestSuite suite(){
              TestSuite suite = new TestSuite("SUITE DI TEST DI PROVA");
             suite.addTestSuite(Servizio01TestB.class);
             suite.addTestSuite(Servizio01Test.class);
             return suite;  
     }
}

Automazione dei casi di test

Ad ogni progetto di implementazione abbiamo finora fatto corrispondere il progetto Ant di compilazione dei sorgenti ed esportazione dei compilati come libreria. Ma Ant dispone anche delle direttive necessarie all'esecuzione automatica di classi JUnit, siano esse codificate sotto forma di casi o di suite di test e di generazione della reportistica con i risultati di esecuzione.

Riportiamo di seguito un file Ant di esempio che automatizza la compilazione e l'esecuzione di un gruppo di test case che corrispondono a una regular expression. Sotto forma di commento sono riportate le direttive che eseguono la suite di test AllLibreria01Test.class.

       
             Compilazione dei test case e loro esecuzione
            
       
       
       
       
                    ="E:/workspaces/rsa/EJBSpring21/SERVIZIO01_TST/junit-4.8.1.jar" />
      
       
      
       
             
             
            
      
       
             
                    
             
          
      
       
       
                                 destdir="${buildDir}" verbose="true" debug="true" />
       
      
       
       
             Esecuzione dei test case JUnit
             
                   
                    
      
                    
                    
                                       
                    
       <!—ESEGUE I JUNIT DEFINITI PER LA SUITE -->
       
       <!—ESEGUE TUTTI JUNIT CHE RISOLVONO IL PATTERN Servizio*Test* -->
             <fileset
             dir="${buildDir}" includes="**/Servizio*Test*.class" />
                    
             
          
      
       

Conclusioni

In questa seconda parte abbiamo iniziato la presentazione dei progetti di implementazione della logica applicativa del componente software in accordo con la modellazione UML. Oltre a risolvere il pattern di sviluppo dichiarato nella modellazione, il workspace è stato arricchito da alcuni progetti e implementazioni di tecnologie per rendere lo sviluppo più flessibile.

Tra queste abbiamo introdotto l'automazione della fase di test attraverso l'integrazione dei framework Ant e JUnit. Questo per rendere robusta l'implementazione del componente sia durante il ciclo di vita della sua versione effettiva che per quella personalizzata, necessaria a che le attività di sviluppo di tutti i gruppi inizino anche quando non sono disponibili i componenti di dipendenza. Chiaramente la trattazione di Ant e JUnit e la loro integrazione sono solo accennate in quanto si tratta di argomenti molto vasti non esauribili se non in uno spazio dedicato.

Nella prossima parte completeremo il workspace di implementazione presentando i progetti connessi alla tecnologia di esecuzione e pubblicazione della logica applicativa, completando in questo senso il pattern di sviluppo che rende indipendente l'implementazione della logica applicativa dalla corrispondente tecnologia di esposizione.

Riferimenti

[1] JUnit

http://www.junit.org/

 

[2] JUnit automation

http://ow.ly/ei9dx

 

[3] JUnit tutorial

http://www.vogella.com/articles/JUnit/article.html

 

[4] Apache Ant

http://ant.apache.org/

 

 

Condividi

Pubblicato nel numero
177 ottobre 2012
Luigi Bennardis si è laureato in Scienze Statistiche ed Economiche all’università di Roma e si occupa di informatica da diversi anni. Dopo una lunga esperienza di system integration su piattaforma Java EE e Microsoft in una importante società di consulenza, attualmente si occupa di progettazione e sviluppo su tecnologie distribuite…
Ti potrebbe interessare anche