Login
Cerca all'interno di JavaPortal
Help
Home Page Documentazione Forum Progetti Partner Pubblica!
Documentazione > Tutorial > ATDD: allineare l’analisi all’implementazione nei progetti IT
Modifica Impostazioni
Hide
Best Practices
EJB
Frameworks
Howto
J2EE
J2ME and Wireless
J2SE
JSP e Servlet
Java Application Server
Java IDE/Tools
Java Media
Java Security
Java Sys Admin
Java e XML
Java e SQL
OpenSource Java
Patterns
Repository
Tesi
UML
Web Services
Slide
White Paper di jws.it
project management
Eventi
Groovy

Hai una tesi in Java?
Tesine preparate
per esami?
Pubblica tutto su
JavaPortal!

Scrivi al nostro staff


Oracle scippa Sun a IBM


Henry Ford
Vero progresso quando i vantaggi di una nuova tecnologia diventano per tutti


Un Bean per interagire con i DataBase


Rss Feed
Home Page
Articoli
News
Forum
Classi

  Visualizza Commenti (1) Aggiungi Commento    
Add to Shortcuts
 
Vota l'articolo
ATDD: allineare l’analisi all’implementazione nei progetti IT
By Stefano Rossini
23 novembre 2009
Valutazione Acquisita: 40

  ATDD: allineare l’analisi all’implementazione nei progetti IT
Program TDD & ATDD
Program Un esempio pratico
Program Conclusioni
Program Bibliografia

Tanti progetti software   falliscono per la mancanza di sufficienti ed adeguati test delle applicazioni. Nella maggior parte dei casi questo il fallimento è dovuto più a problemi organizzativi e culturali che a problemi tecnologici.

Tuttavia  il testing è spesso una pratica trascurata nonostante possa essere un importante metodo di successo del progetto.

In questo articolo si presenteranno due importanti metodologie di test:

  • TDD - Test Driven Development  
  • ATDD - Acceptance Test Driven Development


Verrà inoltre presentato un esempio pratico basato sui framework open source FIT/Fitnesse e JUnit.



TDD & ATDD top

Come suggerito dai nomi, l’Acceptance Test Driven Development (ATDD) e il Test Driven Development (TDD) sono pratiche agili di sviluppo software incentrate sul test.

Queste pratiche stravolgono il  ciclo di sviluppo tradizionale: si crea in primo luogo il codice di test della funzionalità d'interesse, poi il codice applicativo che implementa tale funzione.

In tale ottica lo sviluppo del codice applicativo è subordinato all’esito dell’esecuzione del relativo test. Fino a quando non si otterrà un esito positivo dall’esecuzione del test, lo sviluppo del codice applicativo è da considerarsi non terminato.

Il mantra del TDD si basa su queste due regole:

  1. non scrivere codice applicativo se non si dispone di un test automatico che fallisce
  2. eliminare le eventuali duplicazioni in modo tale da mantenere il codice quanto più possibile semplice

Per quanto riguarda invece gli sviluppi manutentivi e correttivi è fondamentale che per ogni issue (bug o miglioria)  si aggiunga un test in grado di rilevarla prima di rimuoverlo dal codice applicativo.

Figura 1 -  Test Driven Development

Sviluppare secondo il TDD garantisce che per tutto il codice sviluppato esista quantomeno un test d’unità associato (per questo motivo il TDD viene anche chiamato UTDD: Unit Test-Driven Development); in questo modo si arriva a realizzare, parallelamente al codice applicativo, una suite di test completa. Se questo da un lato aumenta la confidenza sulla robustezza del proprio codice sviluppato, dall’altro “semplifica” il refactoring e l’introduzione di nuove funzionalità (lo sviluppatore può sempre contare su una suite consistente di test).

L’ATDD si può considerare come una “estensione” del TDD che anticipa tutta la strategia di test fin dalla fase di analisi e quindi interviene a monte della fase di sviluppo software   del prodotto.

Figura 2 -  TDD: ATDD & UTDD

L’ATDD necessita della partecipazione del cliente  (Customer partecipation) per la definizione di test di accettazione (Acceptance Test) ed i  relativi criteri di accettazione che hanno il compito di specificare il comportamento funzionale desiderato di un sistema /applicazione/componente/servizio software   .

L’ATDD è una tecnica per allineare le esigenze del business e il lavoro svolto dall’IT; questo ciclo di collaborazione e feedback può essere molto produttivo ed anticipare gran parte dei misunderstaning che generalmente accadono quando l’analisi dei requisiti, progettazione e sviluppo vengono effettuati per compartimenti stagni.

L’ATDD crea un meccanismo di feedback tra Cliente, Analisti e Architetti/Sviluppatori software in grado di migliorare la comunicazione e la collaborazione; per ogni  requisito utente (use case, user story, …) si specifica il comportamento che si desidera (dati di input, risultato atteso, errori/eccezioni …..) e sotto quali condizioni (pre/post condizioni) senza utilizzo di tecnicismi come file Java, di properties, XML o altro, ma semplicemente con un tool di word processor, come ad esempio Microsoft Word).

Di fatto l’ATDD anticipa la strategia del TDD nella fase iniziale del ciclo di sviluppo software riducendo il “gap” tra il desiderato del cliente, i requisiti raccolti dall’Analisi e la progettazione/implementazione.

Se da un certo punto di vista l’ATDD e il TDD sono simili in quanto condividono l’idea “core” di creare valore mediante test (test-driven), è bene precisare che obiettivi e ambiti di applicazione sono diversi.

L’ATDD permette di sviluppare il software “giusto” per soddisfare i bisogni del cliente (build the right thing) concentrandosi quindi “sul COSA” si vuole ottenere e cercando di massimizzare la customer satisfaction;  il TDD si focalizza ad un livello inferiore, e guida il corretto e robusto sviluppo del software: “il COME” (build the thing right).

In termini di qualità l’ATDD ci permette di assicurare la qualità esterna del prodotto software mentre il TDD  è più orientato a garantire la qualità interna del prodotto.

Figura 3 -  Qualità del software: esterna ed interna

 



Un esempio pratico top

Vediamo ora un esempio che  mette in pratica quanto spiegato sino ad ora e che mostra l’efficacia della strategia di test appena presentata.

Esistono diversi e ottimi framework open source di testing per lo UTDD come JUnit, SOAP-UI, dbUnit, JSunit ,  Selenium, ecc …

Anche per l’ATDD fortunatamente esistono diversi (ottimi) framework: i due principali sono FIT/Fitnesse  e Robot Framework.

In questo esempio utilizzeremo i framework di test open source JUnit e Fitnesse.

Figura 4 -  Fitnesse & JUnit

Supponiamo di dovere sviluppare un servizio che permetta la gestione dei prelievi da un conto corrente bancario.

Ipotizziamo che le specifiche dei requisiti siano in forma di user story: “Come cliente della Banca voglio essere in grado di prelevare denaro dal mio conto corrente per avere disponibilità liquida”.

Bene, facile! No?

Ok, come potrebbe essere la firma del nostro metodo di business?

Primo tentativo: uno sviluppatore potrebbe creare un metodo che prevede il versamento e che in caso di errore effettui il solo log dell’errore
senza ritornare alcun esito al client:

    public void deposito (double sum) { . . . }  // :-( !

Oppure: si potrebbe sviluppare un metodo, che in modo “old fashion”, preveda come risultato dell’operazione una codifica dell’esito dell’operazione.

    public int deposito (double sum) {  . . .  } // :-(

Altra via: si potrebbe prevedere che il metodo sollevi un’eccezione nel caso in cui l’operazione fallisca

    public void deposito (double sum) throws AccountException{ . . . } // :-|

Riproviamo ancora: prevedere anche di restituire il saldo aggiornato dopo l’operazione

    public double deposito (double sum) throws AccountException{ . . .} // :-)
   
E per quanto riguarda l’implementazione ?

Il requisito non esplicita in che modo si deve comportare il servizio nel caso in cui la cifra da prelevare sia zero o addirittura negativa.
Che tipologia di messaggi di errore si vuole? Con che grado di dettaglio? Ci sono particolari informazioni che si reputa siano importanti da fornire per comunicare il fallimento dell’operazione ?

Con l’ATDD possiamo anticipare tutti questi aspetti che altrimenti sarebbero visibili solo in fase di UTDD del  software  o addirittura a sviluppo ultimato; in entrambi i casi si perderebbe tempo prezioso in dispendiosi ricicli di lavoro.

Utilizzando un framework ATDD come Fitnesse possiamo definire il comportamento (behaviour) del software   che vogliamo venga realizzato.
Nel nostro caso questo si traduce nel condividere con una semplice tabella in formato Word cosa si vuole ottenere come risultato atteso a fronte di determinati input.

Nel documento Word possiamo inserire tutte le spiegazioni del caso (commenti esplicativi e di testo narrativo) basta che il documento contenga delle semplici tabelle (chiamate fixture table) in cui si enfatizzano i dati di input ed il risultato atteso del software  che si sta commissionando.

 

Figura 5 -  Tabella Word per descrizione Acceptance 

Si noti come la tabella della figura 5 sia assolutamente chiara e comprensibile anche a persone di estrazione non tecnica e che si sta utilizzando Microsoft Word e non file di properties,  XML, Java o altro.

Le uniche informazioni tecniche che “sporcano” il documento sono:

  • la prima riga che deve contenere il nome della classe Java (Fixture class) che verrà sviluppata per soddisfare il test
  • le colonne che devono indicare i nomi dei dati di input ed il risultato atteso (seguito da aperta e chiusa parentesi)


Tutto qua.

Automaticamente l’engine di Fitnesse invocherà la classe Java indicata nella tabella (Fixture class) kok.atdd.account.AccountColumnFixtureATDD.
Se il nome del dato della tabella non è seguito da parentesi, l’engine Fit inietterà il valore della colonna in una proprietà pubblica della Fixutre class avente lo stesso nome (numeroContoCorrente e sommaDaPrelevare).
Se il nome è seguito da parentesi, l’engine Fitnesse cercherà di invocare un metodo con quel nome della classe AccountColumnFixtureATDD.

Più facile a farsi che a dirsi.

Nel nostro caso lo sviluppo si traduce nella seguente classe Java:

public class AccountColumnFixtureATDD extends fit.ColumnFixture {
   
    /** Input  */
    /** Numero del conto corrente */
    public String numeroContoCorrente;

    /** Somma da prelevare dal conto corrente */
    public String sommaDaPrelevare;

    /** Output  */
    /** Messaggio di errore nel caso l'operazione non vada a buon fine */
    public String messaggioErrore = null;
   
    /**
     * Ritorna il saldo aggiornato a fronte del prelievo        
     * @return il saldo aggiornato
     * @throws Exception AccountException con il messaggio di errore
     */
    public double saldoOperazione() throws Exception{
       
        << logica di test >>

    }
   
    public String messaggioErrore(){
        . . . .
    }
}


Nel metodo saldoOperazione() c’è il codice di test della nostra classe Java di Business Account:

    public double saldoOperazione() throws Exception{
       
        try{
            Account account = new Account(numeroContoCorrente);
            double res = new AccountService().withdraw(account, new Double(sommaDaPrelevare));
            return res;
        }
        catch(Exception e){
            messaggioErrore=e.getMessage();
            throw e;
        }

    }


L’immagine che segue mostra la corrispondenza tra la tabella word (chiamata in nomenclatura Fitnesse: fixture) e la classe Java AccountColumnFixtureATDD.


Figura 6 -  Tabella Word per descrizione Acceptance Test

Adesso dobbiamo riempire i metodi di test con il codice di test.

Ecco il metodo saldoOperazione():

    /**
     * saldoOperazione: test del metodo  AccountService.withdraw()       
     * @return il saldo aggiornato a fronte del prelievo
     * @throws Exception AccountException con il messaggio di errore
     */
    public double saldoOperazione() throws Exception{
       
        try{
            String methodName = CLASS_NAME + ".saldoOperazione: ";           
                Account account = new Account(numeroContoCorrente, 1000);

                Double res = new AccountService().withdraw(account, new Double(sommaDaPrelevare));

                return res.doubleValue();
        }
        catch(Exception e){
            messaggioErrore=e.getMessage();
            throw e;
        }
    }


La classe AccountService, seguendo il mantra TDD è una classe che semplicemente non fa niente ma è compilabile!

public class AccountService {

    private static final String CLASS_NAME = "AccountService";
      
    /** prelievo
     * @param  somma il valore della somma da prelevare
     * @throws AccountException se l'operazione non è fattibile
     */
        public Double withdraw(Account acc, Double sommaDaPrelevare)
throws AccountException{
           
                return new Double(12345);
       }    


Eseguendo il test con Fitnesse

%JAVA_HOME%\bin\java -classpath .;fitlibrary.jar;fitnesse.jar fit.FileRunner account_userstory.htm resultsAccount.html

ovviamente i risultati del test sono (giustamente) tutti negativi!


Figura 7 -  Tabella Word per descrizione Acceptance Test

Bene, adesso possiamo concentrarci sul codice di business del metodo AccountService.withdraw().

Ecco il codice che permette di visualizzare tutti i test:


    /** prelievo
     * @param  somma il valore della somma da prelevare
     * @throws AccountException se l'operazione non è fattibile
     */
              public Double withdraw(Account acc, Double sommaDaPrelevare) throws AccountException{
           
        String methodName = CLASS_NAME + ".withdraw: ";
        System.out.println(methodName +
                                                 ": Account ID["+acc.getId()+"]-sommaDaPrelevare["+sommaDaPrelevare+"]...");

        double somma = sommaDaPrelevare.doubleValue();
        double saldo = acc.getSaldo();
           
        if (somma < 0){
           String errMSg = methodName + ": somma da prelevare [" + somma +"]NEGATIVA!";
         throw new AccountException(errMSg);
              }
           if (saldo < somma){
               String errMSg =  "#ERRORE# Somma da prelevare [" + somma +"] superiore al saldo[" + saldo +"]!";
                 throw new AccountException(errMSg);
        }
        saldo = saldo - somma;
        acc.setSaldo(saldo);
        return new Double(saldo);
       }    

 

Rieseguendo i test otteniamo che i tre test danno esito positivo: il risultato ottenuto dalla classe Java AccountService  è uguale al risultato atteso definito nella tabella Word.

Abbiamo finito?

No.

E’ vero che il nostro servizio si sta comportando in modo corretto e coerente con quanto richiesto dall’utente.
La sua qualità esterna, cioè “ai morsetti”,  è ottima visto che il codice sviluppato soddisfa tutto quanto concordato con il cliente.

Ma cosa dire riguardo la sua robustezza, cioè riguardo la sua qualità interna?

Il codice è robusto da un punto di vista tecnico? E’ facilmente manutenibile? Nel caso di bug fixing sarà facile effettuare la problem determination?

In altre parole, com’è la sua qualità interna ?

Il TDD con i suoi test di unità (UTDD) ci permette di valutare tutti questi aspetti.

Prepariamo dei test “interni” del servizio e verifichiamo che nel caso di dati di input non corretti il servizio risponda in modo chiaro.

Sviluppiamo alcuni test JUnit che oltre prevedere i dati corretti prevedano anche i dati non corretti, come ad esempio che la somma da prelevare o che l’account siano null.

Ad esempio: i seguenti test che verificano la robustezza del servizio danno rosso:

    public void testPrelievoAccountNull(){
           try {
              accountService.withdraw(null, new Double(500));
             fail("Il test doveva fallire !");
          } catch(AccountException e){
              System.out.println(e.getMessage());
          } catch (Throwable t){
              fail(t.getMessage());
              t.printStackTrace();
          }
    }
   
    public void testPrelievoZero(){
           try {
              accountService.withdraw(account, new Double(0));
             fail("Il test doveva fallire !");
          } catch(AccountException e){
              System.out.println(e.getMessage());
          } catch (Throwable t){
              fail(t.getMessage());
              t.printStackTrace();
          }
    }

    public void testPrelievoSommaNull(){
           try {
              accountService.withdraw(account, null);
             fail("Il test doveva fallire !");
          } catch(AccountException e){
              System.out.println(e.getMessage());
          } catch (Throwable t){
              fail(t.getMessage());
              t.printStackTrace();
          }
    }

    public void testPrelievoAccountNullSommaNull(){
           try {
              accountService.withdraw(null, null);
             fail("Il test doveva fallire !");
          } catch(AccountException e){
              System.out.println(e.getMessage());
          } catch (Throwable t){
              fail(t.getMessage());
              t.printStackTrace();
          }
    }   


 
I test danno rosso e mettono in rilievo una poca robustezza del codice.

 

Figura 8 -  Tabella Word per descrizione Acceptance Test

Si evince che la classe AccountService non effettua alcun controllo sulla validità dei dati in ingresso.

In caso di dati non validi il servizio cade miseramente in NullPointerException senza rispondere con messaggi chiari.

Avendo individuato la criticità possiamo provvedere a modificare il nostro codice affinché i test diventino verde.

Procediamo quindi ad inserire all’ingresso del metodo i controlli della validità sintattica e semantica dei dati di input in modo da avviare l’elaborazione di business se e solo se i dati sono corretti (inutile impegnare risorse preziose come CPU, RAM, rete, database  nel caso di dati errati).

Ecco il codice modificato che permette di rendere verdi tutti i test:

    /** prelievo
     * @param  somma il valore della somma da prelevare
     * @throws AccountException se l'operazione non è fattibile
     */
            public Double withdraw(Account acc, Double sommaDaPrelevare) throws AccountException{
           
        String methodName = CLASS_NAME + ".withdraw: ";

        if(acc == null){
              String errMSg =  "#ERRORE# Account is NULL!";
            System.err.println(methodName + errMSg);
                  throw new AccountException(errMSg);           
        }

        if(sommaDaPrelevare == null){
              String errMSg =  "#ERRORE# Somma da prelevare is NULL!";
            System.err.println(methodName + errMSg);
                  throw new AccountException(errMSg);           
        }

        System.out.println(methodName +
                                                 ": Account ID["+acc.getId()+"]-sommaDaPrelevare["+sommaDaPrelevare+"]...");
       
        double somma = sommaDaPrelevare.doubleValue();
        double saldo = acc.getSaldo();
           
        if (somma <= 0){
            String errMSg = methodName + ": somma da prelevare [" + somma +"]NEGATIVA!";
            System.err.println(errMSg);
               throw new AccountException(errMSg);
              }
           if (saldo < somma){
                 String errMSg =  "#ERRORE# Somma da prelevare [" + somma +"] superiore al saldo[" + saldo +"]!";
              System.err.println(methodName + errMSg);
                    throw new AccountException(errMSg);
        }
                 saldo = saldo - somma;
                   acc.setSaldo(saldo);
                  return new Double(saldo);
       } 

 

Figura 9 -  Tabella Word per descrizione Acceptance Test

Adesso si che ci siamo!

Anche la qualità “interna” è OK.

E’ importante notare come i test UTDD siano di competenza dello sviluppatore che può quindi decidere di esternalizzare i dati del test in file di properties e/o XML e/o DB ecc … o  decidere, per i dati volutamente non corretti, di cablarli nel codice.

A questo punto possiamo provvedere a rilasciare il software  con una qualità esterna (test ATDD) ed interna (UTDD) che ci permetta di essere confidenti sulla bontà del nostro lavoro.

L’esempio utilizzato in questo articolo si è basato su un caso reale di realizzazione di Web Service (semplificato per questioni di chiarezza e di spazio) ma è applicabile anche nel caso di Applicazioni Web, ad esempio  utilizzando il framework di test Selenium.


public class AccountWebTest extends SeleneseTestCase {

    . . . .

    public double saldoOperazione() throws Exception{
       
       try{

        selenium = new DefaultSelenium("127.0.0.1", 8666, "*firefox",
                                   "http://localhost:8080/");
    selenium.start();
        selenium.open("/account/");
        selenium.typeKeys("accountId", "TST123");
        verifyTrue(selenium.isElementPresent("j_id_jsp_saldo"));
    . . .

   
Un’ultima nota prima di concludere. Si è visto che la tabella Word utilizzata per specificare i test di accettazione ed i relativi criteri è “user-friendly”, in quanto di facile interpretazione anche per personale non tecnico.

La sua forma tabellare può essere resa ancora più user-friendly utilizzando linguaggi Domain Specific Language, cioè linguaggi di tipo dichiarativo adatti ad uno specifico dominio, allo scopo di poter specificare i requisiti in linguaggio di business, cioè nel linguaggio “naturale” dello specifico dominio.

Fitnesse permette di specificare un “Testing Domain Specific Language” in modo facile ed intuitivo mediante la classe DoFixture, estendendo la classe fitlibrary.DoFixture ed alternando nella cella delle tabella gli argomenti ed i relativi valori.

Ad esempio, la seguente tabella specifica gli argomenti “preleva dal conto”, “euro” e “verifica saldo finale” con i rispettivi valori: TST124, 100 e 900.

Lato Java la classe di Test estende la classe DoFixture e mette a disposizione il metodo con il nome degli argomenti concatenati tra di loro; nel nostro esempio il nome del metodo è: prelevaDalContoEuroVerificaSaldoFinale() con tre argomenti che indicano rispettivamente il conto, la somma e il saldo da verificare.

public class AccountDoFixtureATDD extends fitlibrary.DoFixture  {

    /**
     * prelevoEuroDalContoIlSaldoDeveEssere
     * @param somma
     * @param conto
     * @param saldo
     * @return res
     */
        public boolean prelevaDalContoEuroVerificaSaldoFinale(String conto,
                                       double somma, double saldoDaVerificare)throws Exception {



Conclusioni top
Sviluppare secondo l’ATDD e UTDD, garantisce che per tutto il codice evolutivo sviluppato (requisito utente, requisito software  , nuova funzionalità, …) e per ogni issue (change request,  anomalia o miglioria) esistano uno o più test associati.

Lo UTDD permette di assicurare la qualità interna del sistema software  mentre l’ATDD ci assicura la qualità esterna e la condivisione dell’accettazione del software  con il cliente.




Figura 10 -  ATDD + TDD  




E’ importante notare come l’ATDD  si possa applicare a livelli diversi di profondità architetturale ed organizzativo.
Queste potenzialità infatti possono essere sfruttate non solo dal cliente/analista, ma anche dall’architetto software/sviluppatore  per guidare la progettazione di servizi/componenti software  in modo corretto e robusto applicando la regola di testing precedentemente descritta (test con input mancanti, test con dati di input non validi, test con input validi) non solo per l’interfaccia grafica ma anche per i servizi di business (es: Web Services) e/o di accesso ai dati (es: Stored Procedure) e/o d’integrazione (es: connettori JBI).




Figura 11 -  ATDD applicato a diversi layer Architetturali

Bibliografia top

- Pratical TDD and Acceptance TDD for Java Developers, L. Koskela - Ed. Manning
- User Stories Applied: For Agile software  Development, Mike Cohn
- Test Driven Development, Scott W. Ambler:  http://www.agiledata.org/essays/tdd.html

Javaportal

- S. Rossini, A. Rocca: La Dependency Injection e il TDD

  • La Dependency Injection e il TDD in Applicazioni Enterprise
  • Parte II: Esempi pratici con Spring
  • Parte III:  Esempi pratici con EJB 3.0


Mokabyte

  • S. Rossini-Pratiche di sviluppo del software  (I)  : TDD: Test Driven Development - MokaByte 86 - Giugno 2004
  • S. Rossini-Pratiche di sviluppo del software  (II) : Continuous Integration: la teoria - MokaByte 87 - Luglio/Agosto 2004
  • S. Rossini-Pratiche di sviluppo del software  (III): Continuous Integration: la pratica - MokaByte 88 - Settembre 2004
  • S. Rossini-Pratiche di sviluppo del software   (IV): Refactoring - Mokabyte 89 - Ottobre 2004
  • S. Rossini-Pratiche di sviluppo del software   (V): Spike Solution - MokaByte 93 - Febbraio 2005
  • S. Rossini-Pratiche di sviluppo del software   (VI): QSA mediante Code Coverage - MokaByte 99 - Settembre 2005
  • S. Rossini-Quality software    Assurance (I): QSA mediante auditing del codice - Mokabyte 90 - Novembre 2004
  • S. Rossini-Quality software    Assurance (II): i Test - Mokabyte 91 - Dicembre 2004


Software

  • JUNIT http://www.junit.org/index.htm
  • DBUNIT http://dbunit.sourceforge.net/howto.html
  • SOPA-UI http://www.soapui.org/
  • SELENIUM http://seleniumhq.org/
  • JSFUNIT http://www.jboss.org/jsfunit/
  • EJB3UNIT http://ejb3unit.sourceforge.net/
  • JMETER http://jakarta.apache.org/jmeter/
  • GRINDER http://grinder.sourceforge.net/
  • FTINESSE http://fitnesse.org/
  • ROBOTFWK  http://code.google.com/p/robotframework/


Username:
Password:
To sign up for an account, click register... Register
Hide





Powered By



Campagna Anti-IF


Skin


PARTNER
Zio Budda
HostingJava


LICENZA



Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons

Sitemap  © 2002-2004 Copyright Information. Privacy . Today is domenica 1 agosto 2010