How to survive your unit tests

71
How to survive your unit tests @racingDeveloper - Gabriele Tondi

Transcript of How to survive your unit tests

Page 1: How to survive your unit tests

How to survive your unit tests@racingDeveloper - Gabriele Tondi

Page 2: How to survive your unit tests

Gabriele Tondi @racingDeveloper

Agile Software Developer

Coordinatore: XPUG-MI

Appassionato di:

sviluppo software, metodologie agili (eXtreme Programming), OOD, TDD e motori

Page 3: How to survive your unit tests

Story time

Page 4: How to survive your unit tests

Greenfield Project

Page 5: How to survive your unit tests

Test Driven Development

Page 6: How to survive your unit tests

First Iteration

Page 7: How to survive your unit tests

Product Owner Status

Page 8: How to survive your unit tests

Developers Status

Page 9: How to survive your unit tests

Nuova Funzionalità

Page 10: How to survive your unit tests

Embrace Change

Page 11: How to survive your unit tests

Cambiamento nel Design

Page 12: How to survive your unit tests

Un piccolo cambiamento, tanti test rotti

Page 13: How to survive your unit tests

Come sopravvivere?

Page 14: How to survive your unit tests

Perché facciamo TDD

• Il codice che abbiamo scritto fa quello che ci aspettiamo?

• feedback immediato sul design

• una suite di test per poter fare refactoring spesso senza paura

Page 15: How to survive your unit tests

Perché i nostri test si oppongono al refactoring?

Page 16: How to survive your unit tests

Spesso:

• Non si riesce a capire perché il test fallisce

• Non si riesce a capire cosa vuole verificare il test

• Il test è fragile

• Il test è troppo accoppiato al codice di produzione

Page 17: How to survive your unit tests

ATTENZIONE: il più delle volte un test è difficile da scrivere a causa di un problema

nel design del codice di produzione.

!

Page 18: How to survive your unit tests

Focus di oggi è sul design dei test realizzati durante le iterazioni TDD

Page 19: How to survive your unit tests

Semplici linee guida. L’insieme fa la differenza.

Page 20: How to survive your unit tests

Un buon test

• è conciso e semplice

• quando fallisce riusciamo subito a capire dove si trova il problema

• rappresenta al meglio un valido esempio di comportamento

Page 21: How to survive your unit tests

Naming

Page 22: How to survive your unit tests

@Test public void testThatValuesLowerThan3AreInvalid() { assertFalse(validator.validate(2)); }

Page 23: How to survive your unit tests

@Test public void testThatValuesLowerThan3AreInvalid() { assertFalse(validator.validate(2)); }

Evitare rumore nel nome dei test

Evitare duplicazione nel nome dei testLasciamo che sia il test stesso a dirci cosa succede

Suggerimento: identificare scenari con i nomi dei testEsempi: tooLowValue, notANumber

Page 24: How to survive your unit tests

@Test

public void tooLowValue() { assertFalse(validator.validate(2)); }

Page 25: How to survive your unit tests

Assert

Page 26: How to survive your unit tests

@Test public void formattedErrorMessage() { assertTrue(formatter.format(NOT_FOUND_ERROR) .equals(“Book not found")); }

java.lang.AssertionError at org.junit.Assert.fail(Assert.java:86) at org.junit.Assert.assertTrue(Assert.java:41) at org.junit.Assert.assertTrue(Assert.java:52)

Page 27: How to survive your unit tests

WTF ?!?!?

Page 28: How to survive your unit tests

@Test public void formattedErrorMessage() { assertEquals(“Book not found”, formatter.format(NOT_FOUND_ERROR)); }

org.junit.ComparisonFailure: Expected :Book not found Actual :Libro non trovato <Click to see difference>

Page 29: How to survive your unit tests

Guardate il test fallire!… sempre! Anche se per qualche motivo già passa.

Page 30: How to survive your unit tests

Suggerimenti

• Usate asserzioni specifiche

• Se non fosse possibile, includete un messaggio

• assertFalse(“value is invalid”, validator.validate(…))

• Fate particolare attenzione ai custom matcher! (Hamcrest)

Page 31: How to survive your unit tests

Act

Page 32: How to survive your unit tests

@Test public void filledCart() { ShoppingCart cart = new ShoppingCart();

cart.add(new Item(“item 1”, 2)); assertEquals(2, cart.total());

cart.remove(“item 1”); assertEquals(0, cart.total());

}

Page 33: How to survive your unit tests

Rischi

• Il test può fallire per due o più comportamenti diversi

• Cosa vogliamo veramente testare?

Page 34: How to survive your unit tests

Suggerimenti• Una sola azione per test

• Evitare act -> assert, act -> assert …

• Sfruttare questo smell per spingere il design

• Nel caso specifico: perché non permettere la creazione di un carrello in uno stato preciso?

Page 35: How to survive your unit tests

Arrange

Page 36: How to survive your unit tests

ATTENZIONE: se il test richiede un setup complesso c’è quasi certamente un problema

con il design del codice di produzione.

!

Page 37: How to survive your unit tests

@Test(expected = MissingTitleException.class) public void missingTitle() { new Book(1, “123485”, null, “an adventure book”); }

@Test(expected = MissingISBNException.class) public void missingISBN() { new Book(1, null, “Survive your unit tests", “an adventure book”); }

@Test(expected = MissingIDException.class) public void missingID() // […]

Page 38: How to survive your unit tests

Rischi

• Quali dati sono importanti per ogni test?

• Se domani il codice ISBN deve essere di almeno 14 caratteri, quanti test devo cambiare?

Page 39: How to survive your unit tests

Suggerimento

• Evitate dettagli inutili per il test

• Rendono più complicato capire cosa è importante per il test

• Rischiano di creare accoppiamento

• Potete evidenziare meglio problemi con il design

Page 40: How to survive your unit tests

Object Mother• Classe con delle fixture pre-impostate

• Esempi:

• BookFixture.newBookWithMissingTitle ( )

• BookFixture.newBookWithMissingISBN ( )

• BookFixture.newBookWithMissingID ( )

Page 41: How to survive your unit tests

Rischio

• Le fixture possono aumentare in maniera poco controllabile

• Questa soluzione potrebbe non scalare abbastanza

Page 42: How to survive your unit tests

Builder

• Una classe che permette di generare un oggetto con uno stato preciso, partendo da valori di default sensibili

Page 43: How to survive your unit tests

public class BookBuilder { private long id = 1; private String title = “A BOOK TITLE”; private String isbn = “123466579490”; private String abstract = “a test book”;

private BookBuilder() {}

public static BookBuilder aBook() { return new BookBuilder();

}

public BookBuilder withTitle(String title) { this.title = title; return this;

}

public Book build() { return new Book(id, title, isbn, abstract);

} }

Page 44: How to survive your unit tests

@Test(expected = MissingTitleException.class) public void missingTitle() { aBook().withTitle(null).build(); }

@Test(expected = MissingISBNException.class) public void missingISBN() { aBook().withISBN(null).build(); }

Page 45: How to survive your unit tests

Vantaggi• Ridotta duplicazione

• i valori di default sono impostati in un unico punto

• Aumentato l’espressività del test

• è molto chiara la correlazione tra input ed output, non dobbiamo specificare valori inutili per il test

Page 46: How to survive your unit tests

Metodi privati nella classe di test

Page 47: How to survive your unit tests

@Test public void obsoleteFlight() { givenAnObsoleteFlight(); whenITryToBookIt(); thenIGetAnError()

}

Page 48: How to survive your unit tests

Vantaggi• l’espressività del comportamento è massima

Rischi• fatico ad ottenere feedback dal design

• posso nascondere di tutto nei metodi privati

• i nomi dei metodi potrebbero mentire!

Page 49: How to survive your unit tests

Suggerimento• usate i metodi privati nei test con attenzione

• ogni metodo privato deve essere di 1 o 2 righe

• lo scopo è quello di generare un Domain Specific Language per il test

• un buon test dovrebbe essere leggibile anche senza metodi privati

Page 50: How to survive your unit tests

Stato globale

Page 51: How to survive your unit tests

@Test public void creationDate() { Date currentDate = new Date(); Book book = new Book();

assertThat(book.creationDate(), greaterThan(currentDate)); }

Page 52: How to survive your unit tests

Suggerimenti

• Evitare il più possibile gli stati globali (ad esempio il tempo)

• È possibile iniettare un collaboratore con ruolo Clock (che può avere una implementazione programmabile)

• Oppure… possiamo passare direttamente il dato che ci interessa!

Page 53: How to survive your unit tests

E quando abbiamo diversi messaggi tra diversi oggetti?

Page 54: How to survive your unit tests

Incoming | Outgoing

Object Under TestIncoming

Outgoing

Page 55: How to survive your unit tests

Command Query Separation• Query:

• un messaggio che torna qualcosa

• non ha side-effect

• Comando

• non torna nulla (void)

• ha dei side-effect

Page 56: How to survive your unit tests

Test Object Under Test

Incoming Query Message

query message

Inviamo un messaggio dal test al oggetto sotto test

response

Oggetto sotto test elabora ed invia una risposta

Verifichiamo la risposta

Page 57: How to survive your unit tests

Test Object Under Test

Incoming Command Message

command message

Inviamo un messaggio dal test al oggetto sotto test

Oggetto sotto test elabora e cambia lo stato

Verifichiamo gli effetti pubblici diretti

simple query on state

Page 58: How to survive your unit tests

@Test public void soldBook() { Book book = new Book(); book.sell();

assertFalse(“book cannot be sold again”, book.canBeSold()); }

Esempio

Page 59: How to survive your unit tests

Test Object Under Testresponse

Outgoing Query Message

query message

Inviamo un messaggio dal test al oggetto sotto test

Verifichiamo la risposta

Collaborator

Oggetto sotto test chiede qualcosa al collaboratore

Page 60: How to survive your unit tests

Integration test?

Page 61: How to survive your unit tests

Quando usare un test double?

• Valore: MAI !

• Entità: A volte

• Servizio: Sempre

Page 62: How to survive your unit tests

Test Object Under Test

query messageresponse

Outgoing Query Message

Non facciamo asserzioni sul messaggio di query tra Object Under Test e collaboratore

Programmiamo il test double con una risposta fissa (stub)

Collaborator test double

Page 63: How to survive your unit tests

@Test public void listManyBooks() { BookRepository bookRepository = context.mock(BookRepository.class); ListBooksUseCase = new ListBooksUseCase(bookRepository);

context.checking(new Expectations() {{ allowing(bookRepository).findAll(); will(returnValue(asList(new Book(), new Book(), new Book()))) }});

List<Book> books = useCase.listBooks();

assertThat(books.count(), is(3)); }

Esempio

Page 64: How to survive your unit tests

Test Object Under Test

Outgoing Command Message

Inviamo un messaggio dal test al oggetto sotto test

Dobbiamo verificare che il comando in uscita sia inviato

Collaboratorcommand message

Oggetto sotto test invia un comando al collaboratore

Page 65: How to survive your unit tests

Test Object Under Test

command message

Outgoing Command MessageCollaborator

MOCK

Impostiamo una expectation sul fatto che il messaggio sia inviato

Page 66: How to survive your unit tests

@Test public void productFound() { ProductRepository productRepository = context.mock(ProductRepository.class); Display display = context.mock(Display.class); PointOfSale pos = new PointOfSale(productRepository, display);

context.checking(new Expectations() {{ allowing(productRepository).find(“__A_BARCODE__”); will(returnValue(new Product(“__PRODUCT_PRICE__”)));

oneOf(display).show(“__PRODUCT_PRICE__”); }});

pos.onBarcode(“__A_BARCODE__”); }

Esempio

Page 67: How to survive your unit tests

Test Object Under Test

Message sent to self (private)

Non facciamo nessuna verifica sul messaggio privato

Page 68: How to survive your unit tests

Perché usare i mock (test double)?

• Se ben usati generano enorme pressione sul design

• Vanno usati come strumento di design

• NON vanno usati al solo scopo di isolare i test

Page 69: How to survive your unit tests

In brevissimo

• Curate i vostri test come curate il codice di produzione

• Fate code-review anche dei test!

• Un buon test oggi può salvarvi da un sacco di lavoro domani

Page 70: How to survive your unit tests

Thank you!

Page 71: How to survive your unit tests

Domande?