7. Automated Test Lifecycle
SUT: System Under Test
Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies
8. Automated Test Lifecycle
SUT: System Under Test
Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies
9. Automated Test Lifecycle
SUT: System Under Test
Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies
10. Example of a Manual Test:
nextLine()
public static void main(String[] args) {
String text = "first linensecond line";
Scanner scanner = new Scanner(text);
System.out.println(scanner.nextLine()); // prints
"first line"
System.out.println(scanner.nextLine()); // prints
"second line”
}
Code:
14. What is TDD?
Test-driven development (TDD) is a software
development process that relies on the repetition of a
very short development cycle:
Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies
Tim Ottinger:http://agileinaflash.blogspot.com/2009/02/red-green-refactor.html
15. RED
first the developer writes a failing automated test case
that defines a desired new behaviour (of the software),
24. Decomposition
Parse the output of “df” process …
When there are no mounted volumes
When there is only one volume mounted
When there are many volumes
When the a volume contains whitespaces
25. First Cycle
Parse the output of “df” process
When there are no volumes
When there is only one volume
When there are many volumes
When the a volume contains whitespaces
26. @Test
public void whenNoVolumes() {
String input = "Filesystem 1024-blocks Used Available Capacity
Mounted on”;
List<String> result = parseDfOutput(input);
assertEquals(emptyList(), result);
}
28. @Test
public void whenNoVolumes() {
String input = "Filesystem 1024-blocks Used Available Capacity
Mounted on”;
List<String> result = parseDfOutput(input);
assertEquals(emptyList(), result);
}
private List<String> parseDfOutput(String string) {
return null;
}
29. @Test
public void whenNoVolumes() {
String input = "Filesystem 1024-blocks Used Available Capacity
Mounted on”;
List<String> result = parseDfOutput(input);
assertEquals(emptyList(), result);
}
private List<String> parseDfOutput(String string) {
return emptyList();
}
31. Second Cycle
Parse the output of “df” process
When there are no volumes
When there is only one volume
When there are many volumes
When the a volume contains whitespaces
32. @Test public void whenOneVolume() {
List<String> result = parseDfOutput(""
+ "Filesystem 1024-blocks Used Available Capacity Mounted
onn"
+ "/dev/disk0s2 243862672 135479924 108126748 56% /");
assertEquals(asList("/"), result);
}
38. Third Cycle
Parse the output of “df” process
When there are no volumes
When there is only one volume
When there are many volumes
When the a volume contains whitespaces
45. Some hints
Keep tests running and passing
Keeps test small
Don’t mix phases (Red, Green,
Refactor)
Don’t mix unit tests with integration
tests
Test only one feature per test
47. Regole applicate
Ogni feature deve essere sviluppata secondo il
TDD
Partire dai test di accettazione
Ritardare le decisioni di design all’ultimo
momento responsabile
Applicare un principio di design solo dopo aver
avuto la prova che sia utile in quel specifico caso
49. Test di accettazione
public class HoroscopeTest {
@Test public void
userListenOroscope() {
Horoscope horoscope = new Horoscope();
horoscope.saveDivination("22-GIU-10", "Ariete",
"Sarai molto fortunato.");
String result =
horoscope.GET("/horoscope/ariete.txt");
assertEquals("22 giugno 2010: Ariete, Sarai
molto fortunato.”, result);
}
50. No test No code
Quando scrivo il codice di produzione scrivo il
minimo necessario a far passare il test
Se il minimo non mi convince (è troppo stupido),
vuol dire che manca una specifica funzionale
cioè manca un test.
Prima di scrivere una qualsiasi riga di codice in
più aggiungo un test che la richieda.
51. @Test public void
shouldStoreDivinationsForMultipleSigns() {
Horoscope horoscope = new Horoscope();
horoscope.saveDivination("22-GIU-10", "Ariete",
"for ariete");
horoscope.saveDivination("22-GIU-10", "Toro",
"for toro");
assertEquals("22 giugno 2010: Ariete, for ariete",
horoscope.GET("/horoscope/ariete.txt"));
assertEquals("22 giugno 2010: Toro, for toro",
horoscope.GET("/horoscope/toro.txt"));
}
52. Cerco di non anticipare il
design
Prima di affrontare lo sviluppo faccio una veloce sessione
di design
Non implemento nessuna decisione fino a che non si
rende necessaria
E.g. anche servirà un DAO per adesso salvo tutto in RAM
54. @Test public void howToCreateMp3() {
Horoscope horoscope = new Horoscope(
aFakeSyntetizerWhichReturns(
aMp3Stream()));
horoscope.saveDivination("22-GIU-10", "Ariete",
"divination");
assertThat(horoscope.GET(
"/horoscope/ariete.mp3").asByteArray(),
is(equalTo(aMp3Stream())));
}
55. Resource
Ora il GET restituisce una Resource
public Resource GET(String path) {...}
Il client decide quale rappresentazione usare:
horoscope.GET("/horoscope/ariete/divination.txt").asString());
horoscope.GET("/horoscope/ariete/divination.mp3").asByteArray();
57. Stato delle cose
Al momento tutto viene persistito in memoria (in una
HashMap)
Non esiste ancora un oggetto DAO, tutto viene fatto
dall’unica class Horoscope
58. Estrazione del comportamento
public class MemoryDivinationRepo {
Divination lastDivinationForSign(String sign);
void saveDivinationFromPronounce(String sign,
String pronounce);
};
public interface Divination {
String asText();
byte[] asMp3();
};
59. Estrazione dell’interfaccia
public class MemoryDivinationRepo
implements DivinationRepo {...}
public interface DivinationRepo {
Divination lastDivinationForSign(String sign);
void saveDivinationFromPronounce(String sign,
String pronounce);
};
60. Caratterizzazione del
comportamento
L’interfaccia è una minima parte del contratto, la parte
più importante è il comportamento che l’oggetto
dovrebbe avere.
Il comportamento lo estraggo con dei test di
caratterizzazione
61. Caratterizzazione di
MemoryDivinationRepo
public class MemoryDivinationRepoTest {
@Test public void
shouldStoreDivinationPronounceForASign() {...}
@Test public void
shouldReplyWithAPronouncedMp3() {...}
@Test public void
shouldStoreDivinationPronounceForMultipleSigns()
{...}
@Test public void
shouldOverrideDivinationPronounce() {...}
}
Questa presentazione è una breve introduzione al Test Driven Development.
Questa è una presentazione rivolta a chi:
- ha sentito parlare della tecnica
- che non l’ha ancora provata o ha appena iniziato
- e che vorrebbe saperne qualcosa di più.
Non c’è nessun argomento avanzato, solo un assaggio.
Gli obiettivi della presentazione sono:
spiegare di cosa si tratta
fornirvi un idea di quello che permette di fare
spiegare i vantaggi dell’approcio
Non è una trattazione completa dell’argomento.
Per riuscire ad usarla con profitto sarebbe necessario almeno leggere alcuni libri che eventualmente dopo indicherò oppure meglio ancora lavorare con qualcuno bravo che la usa.
Questi sono I contenuti della presenazione.
Partiremo dallo spiegare brevemente i concetti che sono dietro alla tecnica.
Esporremo la meccanica alla base della del processo.
Infine vedremo due esemp.
Un esempio sullo sviluppo in TDD di un parser.
Un esempio tratto dal un mio recente lavoro.
Io ho individuato due concetti alla base del TDD.
Test First
Il primo è scrivere il test prima. Cioe’ scriverlo prima che il codice di produzione sia creato.
Questo perche’ l’altra strategia, il test after, non funziona. O almeno funziona male.
I problemi con il test after sono che:
- alla fine il test non viene scritto
- testare del codice che non e’ pensato per essere testato puo’ essere veramente molto difficile
Invece con il test first si riesce a definire a priori sia l’interfaccia del codice da sviluppare sia il comportamento che il codice deve avere.
Automate your tests
L’altro aspetto fondamentale è che tutti i test vengono automatizzati. Il motivo è semplice testare a mano è noioso.
Ricompilare, avviare il programma, inserire gli input necessari per attivare quel particolare pezzo di codice che stiamo scrivendo è lungo e tedioso.
Scrivere test automatici richiede un certo tempo, ma dopo aver fatto un po’ di pratica con gli strumenti il tempo è di meno di quello necessario per scrivere il test automatico.
L’interfaccia si evolve La GET() non era adatta per gestire sia testo che gli mp3
Nasce il concetto di Resource
L’interfaccia si evolve per supportare le nuove funzionalità senza rovinare il design
Nota: refactoring anche dei test scritti precedentemente che saranno adattati alla nuova API.
Nella slide è riportato un esempio di test. Questo esempio di test riguarda lo sviluppo di un applicazione che gestisce il cestino di Linux.
Quando in buttiamo un file nel cestino il sistema si deve ricordare alcuni metadati come il path da cui il file e’ stato “rimosso” e la data in cui questa cosa e’ successa. In Linux queste informazioni sono memorizzate in un file di testo che ha una sintassi simile alla stringa content che vedete nelle slide.
Questo è un test per il metodo parse() della classe TrashInfo e verifica che sia in grado di leggere il contenuto del file passato in input.
La caratteristica importante di questi test è che il controllo dell’esito è automatico, in altre parole che non é necessaria l’ispezione o il controllo da parte dello sviluppatore. L’altro aspetto importante é l’esecuzione é molto veloce perché non ci sono operazioni di I/O.
Questo tipo di test è chiamato test di unità.