✦ Progettazione in XP
✦ Principi di progettazione: Semplicità
✦ Test Driven Development
✦ Self Documenting Code
✦ Once and Only Once
✦ You Ain’t Gonna Need It
✦ Automazione dei test in Java: JUnit
1. Lezione 19: Sviluppo in
Extreme Programming
Corso di Ingegneria del Software
Laurea Magistrale in Ing. Informatica
Università degli Studi di Salerno
1
2. Outline
✦ Progettazione in XP
✦ Principi di progettazione: Semplicità
✦ Test Driven Development
✦ Self Documenting Code
✦ Once and Only Once
✦ You Ain’t Gonna Need It
✦ Automazione dei test in Java: JUnit
2
3. Progettazione in XP
✦ Anche la progettazione viene effettuata in
maniera incrementale
✦ Non c’è una singola figura responsabile
della progettazione
• tutto il team condivide la responsabilità di
progettazione
✦ Non viene prodotta una documentazione
formale della progettazione, specialmente
per la progettazione di dettaglio
3
4. CRC Cards
✦ Lo strumento principale usato durante la
discussione delle scelte progettuali sono
le schede Class-Responsibility-
Collaboration (CRC Cards)
✦ Ogni scheda (tipicamente scritta a mano
durante la discussione) indica:
• il nome di una classe
• le responsabilità della classe
• le altre classi con cui essa interagisce
4
6. CRC Cards
✦ Le schede CRC non fanno parte della
documentazione mantenuta e tracciata
nel progetto
✦ Sono utilizzate esclusivamente come
ausilio durante la discussione, e come
promemoria delle decisioni prese
6
7. The Source Code is the Design
✦ In XP la descrizione ufficiale delle scelte
progettuali non è in un documento
formale, ma è il codice sorgente stesso
(inclusi i commenti)!
• lavorando su piccoli incrementi, non è necessario
avere una documentazione formale dettagliata
per guidare lo sviluppo
• se necessario è possibile “estrarre” con strumenti
automatici una rappresentazione più astratta del
design dal codice sorgente (es. diagrammi UML);
in ogni caso è il codice il riferimento autoritativo
7
8. The System Metaphor
✦ Anche se non si documentano le decisioni
progettuali di basso livello, in genere è
necessaria una descrizione
dell’architettua
✦ A questo scopo si usa la System
Metaphor:
• documento informale e conciso
• descrive in termini di alto livello la struttura del
sistema e i concetti fondamentali, spesso
attraverso analogie
• ha lo scopo di facilitare la comunicazione tra gli
sviluppatori e di stabilire un linguaggio comune
8
9. Progettazione in XP
✦ La linea guida fondamentale:
Do the simplest thing that possibly work
“Tutto ciò che è complesso non è utile.
Tutto ciò che è utile è semplice.”
M. Kalashnikov
9
10. The simplest thing...
✦ La “cosa più semplice” può avere diversi
significati:
• la prima cosa che viene in mente
• la cosa che si realizza con il minore sforzo
• la cosa che può capire anche chi non ha molta
conoscenza/competenza
• ...
✦ Nessuno di questi significati corrisponde
al concetto di semplicità richiesto da XP
10
11. The simplest thing...
✦ Il codice più semplice per risolvere un
problema è quello che:
• supera tutti i test
• esprime ogni idea che gli sviluppatori intendevano
esprimere
• contiene ogni concetto una sola volta
• non ha parti superflue
11
12. Test Driven Development
✦ Il codice che risolve un problema deve
superare tutti i test
✦ Le metodologie tradizionali posizionano il
testing alla fine del ciclo di sviluppo,
quando il codice è completo
✦ XP adotta un approccio diametralmente
opposto: Test Driven Development (TDD)
12
13. Test Driven Development
✦ Il TDD si può riassumere nelle seguenti
raccomandazioni:
• Test Early
• Test Often
• Test Automatically
13
14. Test Early
✦ Nell’aggiungere una nuova funzione al
programma, lo sviluppatore:
• innanzitutto scrive l’interfaccia con
un’implementazione vuota
• quindi scrive il codice per testare la nuova
funzione (unit tests)
• implementa la funzione
• verifica che l’implementazione supera il test
• verifica che tutte le altre funzioni
precedentemente implementate superino i loro
test
• solo a questo punto il lavoro sulla funzione è
14
completo
15. Test Early
✦ Benefici:
• lo sviluppatore verifica l’usabilità dell’interfaccia
della funzione
• lo sviluppatore verifica la sua comprensione dei
requisiti
• il testing non è relegato alla fine del progetto,
quando la prossimità della deadline può spingere
a farlo in maniera frettolosa e approssimativa
• la necessità di testare una singola funzione per
volta spinge lo sviluppatore a progettare codice
con basso accoppiamento
15
16. Test Often
✦ Il codice dei test non è eseguito una volta
sola:
• ad ogni modifica del software vengono eseguiti i
test non solo della parte modificata ma anche di
tutte le altre parti del software
• il codice sviluppato viene integrato nella versione
corrente del software solo se supera tutti i test
16
17. Test Often
✦ Benefici:
• i bug vengono individuati presto nel ciclo di
sviluppo, quando è più semplice circoscriverne la
causa
• gli sviluppatori possono modificare con più
tranquillità il codice esistente, sapendo che se
dovessero introdurre dei bug essi sarebbero
subito rilevati
• il numero di bug che finiscono nella versione
rilasciata del software è significativamente minore
• si evita la “regressione” del software (si parla
anche di “test di regressione”)
17
18. Test Automatically
✦ I test devono essere eseguibili
automaticamente (senza intervento
dell’utente)
✦ Un solo comando deve consentire di
lanciare tutti i test del progetto
✦ Lo sviluppatore deve avere i risultati dei
test in forma sintetica (es. quanti sono i
test falliti) con la possibilità, se ne ha
bisogno, di ottenerli in forma analitica
(quali sono i test falliti)
18
19. Test Automatically
✦ Benefici:
• gli sviluppatori sono incoraggiati a eseguire i test
spesso, dal momento che devono lanciare un
singolo comando
• è possibile usare programmi che lanciano i test
periodicamente (es. ogni notte) e avvisano i
responsabili del progetto dei risultati
• poiché non è possibile automatizzare il testing
dell’interfaccia utente (se non in maniera
limitata), gli sviluppatori sono incoraggiati a
separare l’interfaccia utente dalla business logic
19
20. TDD e Refactoring
✦ Cambiamenti di implementazione ma non
di interfaccia:
• i test già esistenti consentono di verificare che la
nuova implementazione è compatibile con la
vecchia
✦ Cambiamenti di interfaccia a livello
sintattico:
• occorre modificare tutti i test della funzione
✦ Cambiamenti di interfaccia a livello
semantico:
• occorre esaminare tutti i test della funzione per
verificare che siano coerenti con la nuova
20 semantica
21. TDD e debugging
✦ Quando si scopre un bug non rilevato dai
test:
• innanzitutto si crea un nuovo test che fallisca a
causa del bug
• solo a questo punto si provvede a eliminare il bug
✦ Benefici:
• un bug eliminato una volta non rischia di essere
reintrodotto in una successiva modifica del codice
21
22. Self Documenting Code
✦ Il codice più semplice deve esprimere
ogni idea che gli sviluppatori intendevano
esprimere
• non mettere l’implementazione di idee non
correlate nella stessa funzione/classe/metodo
(Alta coesione)
• l’organizzazione delle unità del programma
dovrebbe rendere comprensibile l’organizzazione
delle idee
• i nomi delle entità del programma dovrebbero
rendere chiaro il loro significato minimizzando la
documentazione necessaria
22
23. Self Documenting Code
✦ Principio della Minima Sorpresa: tra le
possibili interpretazioni di un frammento
di programma, quella corretta dovrebbe
essere quella più ovvia per lo sviluppatore
• convenzioni di codifica comuni
• convenzioni comuni per la scelta dei nomi
‣ la difficoltà nella scelta del nome di una classe, funzione o
altra entità, spesso è indice di una cattiva progettazione:
l’entità in questione non ha una singola responsabilità ben
definita
23
24. Self Documenting Code
✦ Benefici
• riduzione della necessità di documentazione
esplicita
‣ uso di strumenti di documentazione automatica come
JavaDoc
‣ eventuali altri tipi di documentazione sono prodotti solo su
richiesta dell’utente
• maggiore semplicità di manutenzione, anche per
sviluppatori diversi dal team iniziale
24
25. Once and Only Once
✦ Ogni informazione deve avere una
rappresentazione unica, non ambigua e
autoritativa all’interno del sistema
• in letteratura si parla anche di “principio
DRY” (Don’t Repeat Yourself)
• vale sia per i dati che per il codice del sistema
• non esclude la presenza di copie “meccaniche”
delle informazioni (es. cache), ma deve essere
unica la rappresentazione a cui gli sviluppatori
fanno riferimento
25
26. Once and Only Once
✦ Benefici:
• è minore il rischio di introdurre incongruenze
• è più facile modificare il sistema
26
27. Once and Only Once
✦ In generale non è banale applicare nel
modo migliore questo principio:
• non sempre è facile riconoscere che due parti del
programma fanno qualcosa di simile
• spesso la duplicazione si manifesta solo astraendo
l’informazione rispetto a un insieme di parametri
✦ Tuttavia ci sono alcune violazioni
grossolane facili da individuare:
• Cut and Paste programming
• costanti letterali nel codice
• ...
27
28. Once and Only Once
✦ Questo principio rappresenta la principale
forza che guida il Refactoring di un
programma:
• periodicamente, dopo l’aggiunta di una o più
funzioni, gli sviluppatori esaminano il programma
per individuare nuove opportunità di rimuovere
duplicazioni
• in questo modo si evita che la struttura del
programma possa “andare alla deriva” durante il
ciclo di sviluppo
28
29. Parti superflue
✦ Il programma non deve contenere parti
superflue
• occorre rimuovere le parti che non sono più
necessarie
• occorre evitare di introdurre nuove funzioni che
non sono ancora necessarie
29
30. Parti superflue
✦ Benefici:
• le parti superflue rendono più complicata la
struttura del programma, e aumentano le sue
dimensioni
• le parti superflue possono contribuire
all’introduzione di bug (es. ARIANE 5)
30
31. Necessità future
✦ Spesso gli sviluppatori hanno la
tentazione di aggiungere nuove funzioni
che non sono attualmente necessarie
• previsione che queste funzioni possano essere
utili in futuro
• previsione che il costo per implementare queste
funzioni sia trascurabile
• queste funzioni sono “interessanti da realizzare”
✦ Questa tendenza può manifestarsi anche
rendendo più generale del necessario una
funzione
31
32. You Ain’t Gonna Need It
✦ In XP si raccomanda di contrastare
questa tendenza:
• gli sviluppatori devono lavorare sui requisiti noti
oggi, non su ipotesi infondate su quelli futuri
• le esigenze future potrebbero essere diverse da
quelle previste
• cercare di anticipare le esigenze future introduce
parti superflue nel programma
✦ Convenzionalmente si usa la frase “You
Ain’t Gonna Need It” (abbreviata in
YAGNI) per indicare questa
raccomandazione
32
33. Automazione dei test in Java
✦ Abbiamo visto che il Test Driven
Development richiede l’automatizzazione
degli Unit Test
✦ Il modo con cui è possibile automatizzare
i test dipende dal linguaggio e
dall’ambiente di sviluppo
✦ Per il linguaggio Java lo standard de facto
è la libreria JUnit
33
34. JUnit
✦ Creata da K. Beck e E. Gamma come
porting al linguaggio Java del framework
SUnit, sviluppato da Beck per il
linguaggio Smalltalk
✦ “Pure Java”, portabile su qualsiasi
piattaforma
✦ Utilizzabile attraverso la command line,
ma anche supportata direttamente da
ambienti di sviluppo come Eclipse
34
35. JUnit
✦ Sito: http://junit.org
✦ Nota:
• faremo riferimento alla versione 4.xx di JUnit, che
richiede il JDK 1.5 o successivi
• per lavorare con JDK precedenti a 1.5 occorre la
versione 3.xx di JUnit, meno semplice da usare
35
36. Installazione di JUnit
✦ Dobbiamo ottenere il file junit-4.xx.jar
dal sito (ad es. la versione corrente è
junit-4.5.jar)
• NOTA: la versione corrente di Eclipse include tra i
plugin di default JUnit 4.3
✦ Il file .jar deve essere inserito nel
classpath di Java sia durante la
compilazione che durante l’esecuzione dei
test
36
37. Uso di JUnit
✦ Tipicamente, per ogni classe da testare si
crea una classe di test; per convenzione il
nome della classe di test si ottiene
aggiungendo “Test” al nome della classe
da testare
• Esempio: se abbiamo una classe Adder, gli unit
test di questa classe saranno nella classe
AdderTest
37
38. Uso di JUnit
✦ Note:
• la classe di test deve essere dichiarata public
• se la classe da testare ha metodi protected o
visibili a livello di package, la classe di test deve
trovarsi nello stesso package
✦ Nel file .java della classe di test occorre
inserire le seguenti direttive:
import org.junit.*;
import static org.junit.Assert.*;
38
39. Uso di JUnit: metodi di test
✦ Nella classe di test, occorre inserire dei
metodi per eseguire i test veri e propri
✦ Per convenzione, il nome di questi metodi
è testNomeMetodo, dove nomeMetodo è il
nome del metodo da testare
✦ I metodi di test devono essere dichiarati
come:
@Test
public void testAdd() {
// implementazione del test
// per il metodo ‘add’
.......
}
39
40. Uso di JUnit: metodi di test
✦ Nota: @Test è una annotazione che
segnala a JUnit che il metodo seguente è
un metodo di test; la classe di test
potrebbe contenere altri metodi ausiliari
che non devono essere eseguiti
direttamente da JUnit
40
41. Uso di JUnit: asserzioni
✦ All’interno dei metodi di test si
richiamano dei metodi il cui nome è
assertCondizione per specificare le
condizioni che devono essere verificate
✦ Al momento dell’esecuzione, un test si
considera superato se tutte le condizioni
specificate risultano verificate
41
42. Uso di JUnit: asserzioni
✦ Principali asserzioni di JUnit:
• assertEquals(expected, actual)
assertArrayEquals(expected, actual)
‣ richiede che il valore ‘expected’ sia uguale a ‘actual’
• assertTrue(cond)
‣ richiede che `cond’ abbia come valore: `true’
• assertFalse(cond)
‣ richiede che `cond’ abbia come valore `false’
• assertNull(obj)
‣ richiede che `obj’ sia un riferimento nullo
• assertNotNull(obj)
‣ richiede che `obj’ sia un riferimento non nullo
42
43. Uso di JUnit: asserzioni
✦ È possibile aggiungere anche una stringa
come primo parametro per identificare la
particolare condizione nell’output del test:
• assertEquals(message, expected, actual)
• assertTrue(message, cond)
• assertFalse(message, cond)
• ...
43
44. Uso di JUnit: esempio
✦ Supponiamo di voler realizzare una classe
che effettui le addizioni tra numeri interi
✦ Seguendo i principi del TDD, cominciamo
a definire l’interfaccia della classe:
public class Adder {
public int add(int a, int b) {
// implementazione vuota!
return 0;
}
}
44
45. Uso di JUnit: esempio
✦ Definiamo ora una classe di test per
Adder nel file “AdderTest.java”:
import org.junit.*;
import static org.junit.Assert.*;
public class AdderTest {
// qui vanno i metodi di test
}
45
46. Uso di JUnit: esempio
✦ Aggiungiamo un metodo per testare il
metodo add di Adder:
import org.junit.*;
import static org.junit.Assert.*;
public class AdderTest {
@Test
public void testAdd() {
// qui va il codice del test
}
}
46
47. Uso di JUnit: esempio
✦ Infine scriviamo il codice del test, usando
le asserzioni per specificare le condizioni
che devono essere verificate:
import org.junit.*;
import static org.junit.Assert.*;
public class AdderTest {
@Test
public void testAdd() {
Adder a=new Adder();
assertEquals(7, a.add(4,3));
assertEquals(5, a.add(10,-5));
}
}
✦ A questo punto la classe di test è completa
e può essere compilata
47
48. Uso di JUnit: esecuzione
✦ Ci sono diversi modi di mandare in
esecuzione i test:
• dalla linea di comando
• creando un main in Java
• direttamente dall’ambiente di sviluppo Eclipse
• ...
48
49. Uso di JUnit: esecuzione
✦ Dalla linea di comando:
• java org.junit.runner.JUnitCore className...
• esempio:
foggia% java org.junit.runner.JUnitCore AdderTest
JUnit version 4.5
.E
Time: 0,008
There was 1 failure:
1) testAdd(AdderTest)
java.lang.AssertionError: expected:<7> but was:<0>
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.failNotEquals(Assert.java:618)
at org.junit.Assert.assertEquals(Assert.java:126)
at org.junit.Assert.assertEquals(Assert.java:145)
at AdderTest.testAdd(AdderTest.java:8)
. . . . .
FAILURES!!!
Tests run: 1, Failures: 1
49
50. Uso di JUnit: esecuzione
✦ Dall’ambiente di sviluppo Eclipse:
• dopo aver selezionato una classe di test, un
package o l’intero progetto, si usa il comando:
‣ Run > Run As... > JUnit Test
50
51. Uso di JUnit: esecuzione
✦ Nota: nel riportare i risultati dei test,
JUnit usa due termini diversi per indicare
i problemi incontrati:
• failure: il test è stato eseguito, e (almeno) una
delle condizioni specificate è risultata falsa
• error: si è verificato un errore che ha impedito
l’esecuzione del test
51
52. Uso di JUnit: time-out
✦ È possibile specificare che un metodo di
test debba considerarsi fallito se non
completa il suo lavoro entro un tempo
prestabilito
• il time-out va specificato in millisecondi, come
parametro dell’annotazione @Test
@Test(timeout=3000) // 3 secondi
public void testAdd() {
// implementazione del test
// per il metodo ‘add’
.......
}
52
53. Uso di JUnit: eccezioni
✦ È possibile specificare che un metodo di
test debba sollevare una specifica
eccezione, oppure si considera fallito:
• l’eccezione va specificata passando l’oggetto Class
che la rappresenta come parametro di @Test
@Test(expected=NotFound.class)
public void testSearch() {
// implementazione del test
// per il metodo ‘search’
.......
}
53
54. Uso di JUnit: Fixture
✦ Spesso occorre realizzare più test che
utilizzano un insieme di oggetti comuni
✦ Dover creare questi oggetti all’interno di
ciascun metodo di test porta a una
duplicazione del codice
✦ Per ovviare a questo inconveniente, in
JUnit si crea una “fixture”: un insieme di
oggetti (associati a variabili di istanza
della classe di test), che vengono
reinizializzati prima di lanciare ogni test
54
55. Uso di JUnit: Fixture
✦ L’annotazione @Before consente di
specificare un metodo che deve essere
lanciato prima di eseguire ciascuno dei
metodi di test
✦ In questo modo è possibile specificare
come inizializzare la fixture prima
dell’esecuzione di ciascun test
✦ È disponibile anche l’annotazione @After
per specificare operazioni da eseguire
dopo ciascun test
55
56. Uso di JUnit: Fixture
import org.junit.*;
✦ Esempio: import static org.junit.Assert.*;
public class AdderTest {
private Adder a;
@Before
public void setUp() {
a=new Adder();
}
@Test
public void testAddPositive() {
assertEquals(7, a.add(4,3));
}
@Test
public void testAddNegative() {
assertEquals(5, a.add(10,-5));
}
}
56