MokaByte 88 - 7mbre 2004 
Pratiche di sviluppo del software
III parte - Continuous Integration: un esempio pratico

di
Strefano Rossini
A.D'Angeli

La Continuous Integration (CI) è una pratica che ha come punto centrale un processo di build e di test completamente automatico che permette ad un team di modificare, compilare e testare un progetto anche piu' volte al giorno (vedere [MOKA_CI_1]). In questo articolo si presenterà un esempio pratico di continous integration esemplificando i concetti spiegati nel precedente articolo.

Un esempio pratico di Continuous Integration
Nell'esempio proposto verrà usata un'applicazione J2EE minimale (la MokaDemo) come "cavia" del processo di CI. Si utilizzerà JBoss come Application server, CVS come sistema di version control, ANT per gli script e il framework JUnit per i test. Il motore per la CI sarà Anthill.
Tutti i prodotti sono open source e liberamente scaricabili da Internet (vedere [JBOSS],[CVS], [JUNIT],[ANT], [ANHILL]).


Figura 1
: Esempio di Continuous Integration

 

Applicazione J2EE di esempio
L'applicazione J2EE (MokaDemo) è volutamente minimale e prevede, lato presentation, una Web App sviluppata sul Framework Struts (vedere [STRUTS]) che si basa sul pattern MVC.
L'applicazione prevede un form che richiede l'inserimento dell'identificativo del cliente per il quale si vogliono vedere visualizzati i dati del conto corrente.


Figura 2
: L'applicazione J2EE d'esempio
(clicca sull'immagine per ingrandire)

 

Una volta inserita la userid, il controller (AccountAction) invoca l'azione di business (GetAccountCommandBean), riceve l'object model (AccountModel) e invoca la vista successiva passandole i dati da visualizzare (AccountView).

public final class AccountAction extends Action {
  public ActionForward execute(….) throws Exception {
    ...
    String userid = (String) PropertyUtils.getSimpleProperty(form, "userid");
    ...
    GetAccountCommandBean cmd = new GetAccountCommandBean(userid);
    cmd = (GetAccountCommandBean) cmd.execute();
    account = cmd.getAccount();
    AccountView av = new AccountView(account); // create view
    ...
    request.setAttribute( Constants.ACCOUNT_KEY, av); // set view into request
    return (mapping.findForward("showAccount")); // Forward control to the specified success URI
  }
}

Il lato Business è incentrato sul Command Design Pattern realizzato attraverso un EJB (CommandEJB) che funziona da "ponte" tra le richieste di servizio (ovvero i "comandi", rappresentati da classi che estendono la classe AbstractCommandBean) e le classi che esaudiscono queste richieste (le quali estendono AbstractCommandTargetBean).
Il server riceve questi comandi (richieste di servizio) generici, li interpreta e li esegue (senza entrare nel merito sul loro tipo) inserendo, nel comando stesso, il risultato.
Questo meccanismo infrastrutturale è reso disponibile allo sviluppatore tramite il package it.mokabyte.j2eedemo.core.
La parte applicativa è costituita da un comando GetAccountCommandBean (l'interfaccia del servizio) che richiede come dato in input l'identificativo dell'utente e come ouput fornisce il modello del conto corrente.

public class GetAccountCommandBean extends AbstractCommandBean{

  private String userid = null; // input
  private AccountOM account = null; // output

  public GetAccountCommandBean(String userid) {
    this.userid=userid;
  }

  public String getTargetCommandBeanName(){
    return "it.mokabyte.j2eedemo.apps.account.command.
             target.GetAccountCommandTargetBean";
  }

  public AccountOM getAccount() {
    return account;
  }

  public String getUserid() {
    return userid;
  }

  public void setAccount(AccountOM accountOM) {
    account = accountOM;
  }

   ...
}

Il reperimento dei dati del conto corrente è eseguito dal target bean GetAccountCommandTargetBean (l'implementazione del servizio) che viene mandato in esecuzione dal Command Facade Bean (CommandEJB).


Figura 3
: Esecuzione del servizio
(clicca sull'immagine per ingrandire)

La parte applicativa "Account" è resa disponibile allo sviluppatore tramite il package it.mokabyte.j2eedemo.apps.account.

 

Il sistema di versionamento CVS
Per la dimostrazione allegata a questo articolo si è scelto il popolare sistema di versionamento Open-Source CVS. A titolo informativo, si forniscono di seguito alcune precisazioni sulle peculiarità di tale sistema e sulla terminologia dei suoi comandi che si discosta leggermente da quella 'standard' descritta in [MOKA_CI_1]. La prima caratteristica, che è insita nel nome, è quella di essere un sistema di versionamento 'concorrente' (CVS=Concurrent Versions System). Questo significa che a differenza di altri sistemi, CVS non prevede di default la necessità di effettuare un lock sul file di cui si esegue il checkout per modificarlo. Quindi in linea di principio, può accadere benissimo che due sviluppatori (magari situati in due angoli remoti del pianeta) eseguano il checkout di uno stesso file nello stesso momento. Quando avranno compiuto le loro modifiche dovranno quindi eseguire il check-in: il primo che effettua questa operazione non avrà nessun problema: aggiornerà il file contenuto nel repository con la propria versione modificata. Il secondo sviluppatore che tenta il check-in però si vedrà notificare da CVS che nel frattempo qualcun altro ha già apportato delle modifiche al repository e quindi evidenzierà quali sono gli eventuali conflitti fra le modifiche dei due sviluppatori. Dovrà essere cura di questo secondo sviluppatore, meglio se di concerto con il primo che ha modificato il file, apportare le proprie modifiche in modo da non annullare quelle dell'altro.
Per quanto riguarda la terminologia, si noterà che nel mondo CVS l'operazione di check-in viene generalmente chiamata commit, mentre al termine checkout viene associata in CVS solo l'operazione con cui si popola per la prima volta un workspace locale con un file tratto dal repository, mentre i successivi aggiornamenti dal repository all'area locale vengono detti update.

 

Gli script ANT
Per l'esempio proposto in questo articolo si è scelto di usare Apache Ant per la creazione degli script di automazione del build. Apache Ant è un tool basato su Java e XML. Il principale vantaggio dell'uso combinato di queste due tecnologie è l'assoluta indipendenza dalla piattaforma; l'unico requisito è quello di avere una JVM.
Ogni buildfile di ANT è organizzato in un progetto caratterizzato da un nome , da un target di default e da una directory base.
Ogni build di ANT definisce uno o piu' target; un target a sua volta è un insieme di task che vengono processati in sequenza.
E' possibile passare, tramite la linea di comando, il target da eseguire; in assenza di questo viene eseguito quello di default.

java -cp %CLASSPATH% org.apache.tools.ant.Main -buildfile .\ant\build.xml make-all

E' possibile definire delle relazioni di dipendenza tra i target ad esempio mediante la keyword depends che definisce l'ordine con cui devono essere eseguiti i target. Ciò consente di costruire delle pipeline (catene di montaggio) di task in cui un task costruisce un prodotto parziale che viene poi usato come punto di partenza da un task successivo e così via.
I task rappresentano l'unità atomica di esecuzione di ANT e sono implementati attraverso classi Java. Sono divisi in famiglie (di compilazione, di deployment, ecc…) e possono riferire proprietà o classpath definiti da altri task.
All'interno del processo di sviluppo, ANT può essere utilizzato per compilare il codice, creare EJB, farne il deploy/undeploy, lanciare test, ecc … il tutto in maniera completamente automatica e svincolata da qualsiasi IDE.
Di seguito si riporta come esempio il file build.xml che permette la compilazione dell'intera applicazione J2EE, dove il target make-all funziona da "Master Build" andando ad invocare il target make-all sui sottoprogetti Apps, Core e Web per poi provvedere al deploy dei vari archivi ottenuti.


<target name="make-all" depends="deploy">
</target>

<target name="deploy" depends="make_core,make_apps,make_webapp">
<copy file="${bin.dir}/mokaCoreCommon.jar" toDir="${jboss-deploy.dir}"/>
<copy file="${bin.dir}/mokaCoreClient.jar" toDir="${jboss-deploy.dir}"/>
<copy file="${bin.dir}/mokaModules_client.jar" toDir="${jboss-deploy.dir}"/>
. . . . .
</target>

<target name="make_core" depends="init">
<ant antfile="${basedir}/MokaCore/ant/build.xml" dir="${basedir}/MokaCore" target="make-all" />
</target>


<target name="make_apps" depends="init,make_core">
<ant antfile="${basedir}/MokaApps/ant/build.xml" dir="${basedir}/MokaApps" target="make-all" />
</target>

<target name="make_webapp" depends="init,make_apps">
<ant antfile="${basedir}/MokaAppsWeb/ant/build.xml" dir="${basedir}/MokaAppsWeb" target="make-all" />
</target>

I make-all dei sotto progetti Apps, Core e Web hanno all'interno del loro build.xml il target make-all che provvede a compilare e a creare gli opportuni archivi.

<target name="make-all" depends="makejar_core">
</target>

<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${classes.dir}" debug="yes" >
<classpath>
<fileset dir="${jboss.dist.dir}/server/default/lib">
<include name="*.jar"/>
</fileset>
<fileset dir="${junit.dist.dir}">
<include name="*.jar"/>
</fileset>
. . . .
</classpath>
</javac>
</target>

<target name="makejar_core_client" depends="compile">
<jar jarfile="${bin.dir}/mokaCoreClient.jar" manifest="${deploy.dir}/ejb/Manifest.mf">
<fileset dir="${classes.dir}" >
<include name= "it/mokabyte/j2eedemo/core/command/*"/>
<include name= "it/mokabyte/j2eedemo/core/command/exception/**"/>
<include name= "it/mokabyte/j2eedemo/core/util/**"/>
. . . . . .
</fileset>
</jar>
</target>

 

I test con JUnit
Un processo di CI deve immancabilmente prevedere la presenza di una suite di test di non regressione per verificare la correttezza del codice da costruire.
JUnit è un framework nato per semplificare lo sviluppo di test di unità. E' integrabile in ANT mediante il task (opzionale) junit.
Nell'esempio che segue si riporta un estratto della classe di test che verifica il funzionamento del comando di business GetAccountCommandBean:

public class TestApps extends TestCase {

  public static Test suite() {
    TestSuite suite = new TestSuite("Test Apps");
    suite.addTest(new TestApps("testAccountDao"));
    suite.addTest(new TestApps("testAccountCommandBean"));
    ...
    return suite;
  }

  public void testAccountCommandBean() {
    try {
      GetAccountCommandBean cmd = new GetAccountCommandBean(USERID);
      cmd = (GetAccountCommandBean) cmd.execute();
      AccountOM result = cmd.getAccount();
      assertNotNull(result);
      assertEquals("CHECK CONFRONTO SALDO", result.getBalance(), BALANCE, 0);
    } catch (Exception e) {
      fail(e.getMessage());
    }
  }
}

Il lancio dei test viene schedulato come ultimo passo della CI ed è invocato tramite ANT che è anche in grado di generare un report HTML della Test Suite tramite l'utilizzo del tag <junitreport>

<target name="run_testclient" depends="compile">
<junit fork="yes">
<jvmarg value="-Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory"/>
<jvmarg value="-Djava.naming.provider.url=jnp://${jboss.host}:1099"/>
<jvmarg value="-DMDConfigurationRoot=${basedir}/../config"/>

<formatter type="plain"/>

<test name="it.mokabyte.j2eedemo.test.core.TestCore" haltonfailure="no" outfile="resultCore">
<formatter type="xml"/>
</test>
<test name="it.mokabyte.j2eedemo.test.apps.TestApps" haltonfailure="no" outfile="resultApps">
<formatter type="xml"/>
</test>
<classpath>
<dirset dir="${classes.dir}"/>
<fileset dir="${bin.dir}" includes="*.jar"/>
<pathelement location="${jboss.dist.dir}/client/jboss-j2ee.jar"/>
<pathelement location="${jboss.dist.dir}/client/jbossall-client.jar"/>
. . . . . . . . . .
<pathelement location="${xerces.dist.dir}/xercesImpl.jar"/>
</classpath>
</junit>
<junitreport todir=".">
<fileset dir=".">
<include name="result*.xml"/>
</fileset>
<report format="frames" todir="${dist.tests.dir}"/>
</junitreport>
</target>



Il tool di Continuous Integration Anthill
Fra i tool di Continuous Integration disponibili, si è scelto di utilizzare Anthill, che risulta molto facile da installare e configurare. Altri prodotti simili sono CruiseControl realizzato dalla ThoughtWorks e Gump che fa parte del progetto jakarta-apache. Anthill è disponibile sia come progetto OpenSource che si può scaricare liberamente dal sito [ANT_HILL] sia come progetto commerciale, che fornisce diverse funzionalità a completamento di quelle presenti nella versione Open.

Il cuore di Anthill è costituito da una WebApp, che viene fornita tramite un archivio war che va deployato così com'è in un servlet container (non fornito con Anthill). Noi abbiamo utilizzato Tomcat (versione 4.0.19). Una volta fatto il deploy, dovreste poter raggiungere l'applicazione anthill tramite un url del tipo http://localhost:8080/anthill


Figura 4
: Schermata iniziale di Anthill


Figura 5
: Impostazioni Anthill del progetto di esempio

A questo punto potete creare un nuovo progetto in AntHill agendo sull'apposito link "Create New Project". Si dovranno inserire informazioni generali, quali il nome del progetto in Anthill, il path, relativo alla root del progetto, dove si trova lo script di ant che avvia il build, gli indirizzi e-mail degli utenti che dovranno essere notificati dopo la conclusione dei build schedulati ecc. Inoltre sarà necessario configurare le informazioni specifiche a CVS, come il nome del 'module' CVS che contiene il Progetto in CVS; la CVSROOT da utilizzare per collegarsi al vostro server CVS e l'utente che figurerà come l'autore delle operazioni fatte da Tomcat/Anthill sul Repository.


Figura 6
: Impostazioni di Anthill per CVS
(clicca sull'immagine per ingrandire)

Un'ultima impostazione da effettuare riguarda il path (sempre relativo alla root del progetto) di un file di testo che è necessario mettere sotto version control e che è preposto a contenere il numero del build corrente. Attraverso il seguente form si imposta il nome scelto per tale file. Il suo contenuto iniziale dovrà essere una stringa del tipo "1.0.0". Ad ogni successivo build, Anthill provvederà ad aggiornare la terza cifra con un numero progressivo. Le prime due cifre viceversa, dovranno essere cambiate manualmente, per indicare cambi di versione 'Major' e 'Minor'.


Figura 7
: Impostazioni del file che contiene il build number

Una volta terminate le necessarie impostazioni, è possibile defiire una politica di scheduling che esegua il build ad intervalli specificati. E' d'altra parte possibile in qualunque momento invocare un build non schedulato.

Anthill non richiede di effettuare alcuna modifica agli script di build; tuttavia è utile tenere presente che quando Anthill invoca un certo build.xml, passa implicitamente una variabile chiamata "deployDir", valorizzata in modo da puntare ad una directory 'pubblicata' dalla WebApp di Anthill e specifica al progetto in questione. E' quindi molto utile sfruttare questa variabile all'interno dei propri build file, collocando in tale path gli eventuali 'prodotti' della compilazione, quali ad esempio:

  • Esiti dei test (ottenuti con il task <junitreport>)
  • Javadoc
  • Binari prodotti dalla compilazione

Per far sì che gli script di build funzionino anche al di fuori del sistema Anthill, si può impostare la variabile "deployDir" all'interno degli script in modo che puntino ad una locazione per esempio all'interno dell'alberatura del progetto. Così, quando viene lanciato lo script di build autonomamente, i risultati del build verranno collocati in tale directory. Viceversa, qualora lo script sia invocato da Anthill, il valore specificato all'interno dello script verrà sovrascritto da quello passato da Anthill.


La directory di pubblicazione dei risultati di un build può essere acceduta dal link presente nella schermata iniziale che si chiama come il progetto (nel nostro esempio moka_demo). Nel nostro caso, da tale link si potrà accedere alle pagine contenenti gli esiti dei test e pure alle pagine con i log delle compilazioni eseguite, che vengono prodotte automaticamente da Anthill nella directory 'buildLogs'.


Figura 8: Report dei test
(clicca sull'immagine per ingrandire)

 

Conclusioni
In questo articolo si è presentato un esempio pratico di Continuos Integration esemplificando i concetti introdotti in [MOKA_CI_1].

 

Bibliografia
[MOKA_CI_1] S. Rossini, A.D'Angeli: Continuous Integration: la teoria - Mokabyte N.87 Luglio/Agosto 2004
[MFCI]Continuous Integration: Martin Fowler
http://martinfowler.com/articles/continuousIntegration.html
[JBOSS] www.jboss.org
[CVS] http://www.loria.fr/~molli/cvs/doc/cvs_toc.html
[STRUTS] http://struts.apache.org/
[ANT] http://ant.apache.org/index.html
[ANTHILL] http://www.urbancode.com/projects/anthill/default.jsp
[xUNIT] http://www.xprogramming.com/software.htm
[JUNIT] http://www.junit.org/index.htm
[TWCC] http://cruisecontrol.sourceforge.net


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