Download - How to survive your unit tests
![Page 1: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/1.jpg)
How to survive your unit tests@racingDeveloper - Gabriele Tondi
![Page 2: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/2.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/3.jpg)
Story time
![Page 4: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/4.jpg)
Greenfield Project
![Page 5: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/5.jpg)
Test Driven Development
![Page 6: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/6.jpg)
First Iteration
![Page 7: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/7.jpg)
Product Owner Status
![Page 8: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/8.jpg)
Developers Status
![Page 9: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/9.jpg)
Nuova Funzionalità
![Page 10: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/10.jpg)
Embrace Change
![Page 11: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/11.jpg)
Cambiamento nel Design
![Page 12: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/12.jpg)
Un piccolo cambiamento, tanti test rotti
![Page 13: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/13.jpg)
Come sopravvivere?
![Page 14: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/14.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/15.jpg)
Perché i nostri test si oppongono al refactoring?
![Page 16: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/16.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/17.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/18.jpg)
Focus di oggi è sul design dei test realizzati durante le iterazioni TDD
![Page 19: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/19.jpg)
Semplici linee guida. L’insieme fa la differenza.
![Page 20: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/20.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/21.jpg)
Naming
![Page 22: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/22.jpg)
@Test public void testThatValuesLowerThan3AreInvalid() { assertFalse(validator.validate(2)); }
![Page 23: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/23.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/24.jpg)
@Test
public void tooLowValue() { assertFalse(validator.validate(2)); }
![Page 25: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/25.jpg)
Assert
![Page 26: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/26.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/27.jpg)
WTF ?!?!?
![Page 28: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/28.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/29.jpg)
Guardate il test fallire!… sempre! Anche se per qualche motivo già passa.
![Page 30: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/30.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/31.jpg)
Act
![Page 32: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/32.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/33.jpg)
Rischi
• Il test può fallire per due o più comportamenti diversi
• Cosa vogliamo veramente testare?
![Page 34: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/34.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/35.jpg)
Arrange
![Page 36: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/36.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/37.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/38.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/39.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/40.jpg)
Object Mother• Classe con delle fixture pre-impostate
• Esempi:
• BookFixture.newBookWithMissingTitle ( )
• BookFixture.newBookWithMissingISBN ( )
• BookFixture.newBookWithMissingID ( )
![Page 41: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/41.jpg)
Rischio
• Le fixture possono aumentare in maniera poco controllabile
• Questa soluzione potrebbe non scalare abbastanza
![Page 42: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/42.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/43.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/44.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/45.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/46.jpg)
Metodi privati nella classe di test
![Page 47: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/47.jpg)
@Test public void obsoleteFlight() { givenAnObsoleteFlight(); whenITryToBookIt(); thenIGetAnError()
}
![Page 48: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/48.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/49.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/50.jpg)
Stato globale
![Page 51: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/51.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/52.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/53.jpg)
E quando abbiamo diversi messaggi tra diversi oggetti?
![Page 54: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/54.jpg)
Incoming | Outgoing
Object Under TestIncoming
Outgoing
![Page 55: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/55.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/56.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/57.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/58.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/59.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/60.jpg)
Integration test?
![Page 61: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/61.jpg)
Quando usare un test double?
• Valore: MAI !
• Entità: A volte
• Servizio: Sempre
![Page 62: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/62.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/63.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/64.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/65.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/66.jpg)
@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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/67.jpg)
Test Object Under Test
Message sent to self (private)
Non facciamo nessuna verifica sul messaggio privato
![Page 68: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/68.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/69.jpg)
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](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/70.jpg)
Thank you!
![Page 71: How to survive your unit tests](https://reader031.vdocument.in/reader031/viewer/2022021921/58f9ad77760da3da068b987f/html5/thumbnails/71.jpg)
Domande?