Introduzione al TDD

Post on 01-Dec-2014

1.466 views 7 download

Tags:

description

Introduzione al TDD con esempi e consigli.

Transcript of Introduzione al TDD

Introduzione al

Test Driven Development

Andrea Francia

http://www.andreafrancia.it

ContentsConceptsDefinitionExample1: ParserExample2: Text To Speech

Concepts

Test FirstAutomate tests

What is a Automated Test?

Automated Test Lifecycle

Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies

Automated Test Lifecycle

Giordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies

Automated Test Lifecycle

SUT: System Under TestGiordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies

Automated Test Lifecycle

SUT: System Under TestGiordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies

Automated Test Lifecycle

SUT: System Under TestGiordano Scalzo: http://www.slideshare.net/giordano/tdd-iphonefordummies

Example of a Manual Test: nextLine()

public static void main(String[] args) {String text = "first line\nsecond line";

Scanner scanner = new Scanner(text);

System.out.println(scanner.nextLine()); // prints "first line"System.out.println(scanner.nextLine()); // prints "second line”

}

Code:

assertEquals(expected, actual);

PASS FAIL

expected == actual

yes no

Using JUnit@Test public void howNextLineWorks() throws IOException {

String text = "first line\nsecond line";

Scanner scanner = new Scanner(text);

assertEquals(”first line", scanner.nextLine()); assertEquals(”second line", scanner.nextLine()); }

Code:

Output:

What is Test Driven Development?

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-iphonefordummiesTim Ottinger:http://agileinaflash.blogspot.com/2009/02/red-green-refactor.html

RED

first the developer writes a failing automated test case that defines a desired new behaviour (of the software),

GREEN

then produces code to pass that test …

Refactor

and finally refactors the new code to acceptable standards.

Example:Writing “df” Parser

The df output[andreafrancia@deneb Dropbox]$ dfFilesystem 1K-blocks Used Available Use% Mounted on/dev/disk0s2 243862672 135971832 107634840 56% //dev/disk0s1 243862672 135971832 107634840 56% /tmp/dev/disk1s2 243862672 135971832 107634840 56% /optdevfs 109 109 0 100% /dev

Mount Points

The problemList all mount points

The ProblemWrite a method that extract the list of the mount

points from the output of df.

First of all decompose the

problem!

DecompositionParse the output of “df” process …

When there are no mounted volumes When there is only one volume mountedWhen there are many volumes When the a volume contains whitespaces

First CycleParse the output of “df” process

When there are no volumes When there is only one volumeWhen there are many volumesWhen the a volume contains whitespaces

@Test public void whenNoVolumes() { String input = "Filesystem 1024-blocks Used Available

Capacity Mounted on”; List<String> result = parseDfOutput(input);

assertEquals(emptyList(), result); }

@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; }

@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(); }

No need for refactoring

Second CycleParse the output of “df” process

When there are no volumes When there is only one volumeWhen there are many volumesWhen the a volume contains whitespaces

@Test public void whenOneVolume() { List<String> result = parseDfOutput("" + "Filesystem 1024-blocks Used Available Capacity

Mounted on\n" + "/dev/disk0s2 243862672 135479924 108126748

56% /");

assertEquals(asList("/"), result); }

private List<String> parseDfOutput(String string) { return emptyList(); }

private List<String> parseDfOutput(String string) { List<String> result = new ArrayList<String>();

return result;}

private List<String> parseDfOutput(String input) { List<String> result = new ArrayList<String>();

Scanner scanner = new Scanner(input); scanner.nextLine(); // skip header if(scanner.hasNextLine()) { String line = scanner.nextLine(); Scanner scanner1 = new Scanner(line); scanner1.next(); // skip Filesystem scanner1.next(); // skip 1024-blocks scanner1.next(); // skip Used scanner1.next(); // skip Available scanner1.next(); // skip Capacity String mountPoint = scanner1.next(); result.add(mountPoint); }

return result; }

private List<String> parseDfOutput(String input) { List<String> result = new ArrayList<String>();

Scanner lines = new Scanner(input); lines.nextLine(); // skip header if(lines.hasNextLine()) { String line = lines.nextLine(); Scanner values = new Scanner(line); values.next(); // skip Filesystem values.next(); // skip 1024-blocks values.next(); // skip Used values.next(); // skip Available values.next(); // skip Capacity String mountPoint = values.next(); result.add(mountPoint); }

return result; }

private List<String> parseDfOutput(String input) { List<String> result = new ArrayList<String>();

Scanner lines = new Scanner(input); lines.nextLine(); // skip header if(lines.hasNextLine()) { result.add(parseMountPoint(lines.nextLine())); }

return result; }

private String parseMountPoint(String line) { Scanner values = new Scanner(line); values.next(); // skip Filesystem values.next(); // skip 1024-blocks values.next(); // skip Used values.next(); // skip Available values.next(); // skip Capacity String mountPoint = values.next(); return mountPoint; }

Third CycleParse the output of “df” process

When there are no volumes When there is only one volumeWhen there are many volumesWhen the a volume contains whitespaces

@Test public void whenMultipleVolume() { List<String> result = parseDfOutput("" + "Filesystem 1024-blocks Used Available Capacity

Mounted on\n" + "/dev/disk0s2 243862672 135479924 108126748 56% /\

n" + "/dev/disk0s2 243862672 135479924 108126748

56% /media/disk\n" + "/dev/disk0s2 243862672 135479924 108126748 56%

/tmp\n");

assertEquals(asList("/", "/media/disk", "/tmp"), result); }

@Test public void whenMultipleVolume() { List<String> result = parseDfOutput("" + "Filesystem 1024-blocks Used Available Capacity

Mounted on\n" + "/dev/disk0s2 243862672 135479924 108126748 56% /\

n" + "/dev/disk0s2 243862672 135479924 108126748

56% /media/disk\n" + "/dev/disk0s2 243862672 135479924 108126748 56%

/tmp\n");

assertEquals(asList("/", "/media/disk", "/tmp"), result); }

private List<String> parseDfOutput(String input) { List<String> result = new ArrayList<String>();

Scanner lines = new Scanner(input); lines.nextLine(); // skip header if(lines.hasNextLine()) { String line = lines.nextLine(); String mountPoint = parseMountPoint(line); result.add(mountPoint); }

return result; }

private List<String> parseDfOutput(String input) { List<String> result = new ArrayList<String>();

Scanner lines = new Scanner(input); lines.nextLine(); // skip header while(lines.hasNextLine()) { String line = lines.nextLine(); String mountPoint = parseMountPoint(line); result.add(mountPoint); }

return result; }

TDD Rules

TDD RulesTest FirstTest for All FeaturesRemove all duplication (always)

Some hintsKeep tests running and passingKeeps test smallDon’t mix phases (Red, Green,

Refactor)Don’t mix unit tests with

integration testsTest only one feature per test

Examples from my last work

Regole applicateOgni feature deve essere sviluppata

secondo il TDDPartire dai test di accettazioneRitardare le decisioni di design all’ultimo

momento responsabileApplicare un principio di design solo dopo

aver avuto la prova che sia utile in quel specifico caso

Prima feature: produrre testo pronunciabile

Test di accettazionepublic 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);

}

No test No codeQuando 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.

@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"));}

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

Seconda Feature: oroscopo ascoltabile

@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())));}

ResourceOra 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();

Terza feature: la persistenza

Stato delle coseAl momento tutto viene persistito in memoria (in

una HashMap)Non esiste ancora un oggetto DAO, tutto viene

fatto dall’unica class Horoscope

Estrazione del comportamento

public class MemoryDivinationRepo {

Divination lastDivinationForSign(String sign); void saveDivinationFromPronounce(String sign, String pronounce);

};

public interface Divination { String asText(); byte[] asMp3();};

Estrazione dell’interfacciapublic class MemoryDivinationRepo implements DivinationRepo {...}

public interface DivinationRepo { Divination lastDivinationForSign(String sign); void saveDivinationFromPronounce(String sign, String pronounce);};

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

Caratterizzazione di MemoryDivinationRepo

public class MemoryDivinationRepoTest { @Test public void shouldStoreDivinationPronounceForASign() {...}

@Test public void shouldReplyWithAPronouncedMp3() {...}

@Test public void shouldStoreDivinationPronounceForMultipleSigns() {...}

@Test public void shouldOverrideDivinationPronounce() {...}}

Thanks

Andrea Franciawww.andreafrancia.itblog.andreafrancia.it

andrea@andreafrancia.it

Extra slides

Example of an Automated Test

@Test public void shouldParsePath() {

String content = "[Trash Info]\n" + "Path=/home/andrea/foo.txt\n" + "DeletionDate=2010-08-23T12:59:14\n";

String path = Parser.parsePath(content);

assertEquals("/home/andrea/foo.txt”, path);}

Testing Frameworks

http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks

•PHPUnit•SimpleTest

•Google C++ Testing Framework•CppUnitLite•CppUnit

•JUnit•TestNG

•PyUnit•Nose