Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
How to survive your unit tests
@racingDeveloper - Gabriele Tondi
Gabriele Tondi @racingDeveloper
Agile Software Developer
Coordinatore: XPUG-MI
Appassionato di:
sviluppo software, metodol...
Story time
Greenfield Project
Test Driven Development
First Iteration
Product Owner Status
Developers Status
Nuova Funzionalità
Embrace Change
Cambiamento nel Design
Un piccolo cambiamento, tanti test rotti
Come sopravvivere?
Perché facciamo TDD
• Il codice che abbiamo scritto fa quello che ci aspettiamo?
• feedback immediato sul design
• una sui...
Perché i nostri test si
oppongono al refactoring?
Spesso:
• Non si riesce a capire perché il test fallisce
• Non si riesce a capire cosa vuole verificare il test
• Il test è...
ATTENZIONE: il più delle volte un test è
difficile da scrivere a causa di un problema
nel design del codice di produzione.
!
Focus di oggi è sul design dei test
realizzati durante le iterazioni TDD
Semplici linee guida.
L’insieme fa la differenza.
Un buon test
• è conciso e semplice
• quando fallisce riusciamo subito a capire dove si trova il
problema
• rappresenta al...
Naming
@Test
public void testThatValuesLowerThan3AreInvalid()
{
assertFalse(validator.validate(2));
}
@Test
public void testThatValuesLowerThan3AreInvalid()
{
assertFalse(validator.validate(2));
}
Evitare rumore nel nome dei...
@Test
public void tooLowValue()
{
assertFalse(validator.validate(2));
}
Assert
@Test
public void formattedErrorMessage()
{
assertTrue(formatter.format(NOT_FOUND_ERROR)
.equals(“Book not found"));
}
jav...
WTF ?!?!?
@Test
public void formattedErrorMessage()
{
assertEquals(“Book not found”,
formatter.format(NOT_FOUND_ERROR));
}
org.junit...
Guardate il test fallire!
… sempre! Anche se per qualche motivo già passa.
Suggerimenti
• Usate asserzioni specifiche
• Se non fosse possibile, includete un messaggio
• assertFalse(“value is invalid...
Act
@Test
public void filledCart()
{
ShoppingCart cart = new ShoppingCart();
cart.add(new Item(“item 1”, 2));
assertEquals(2, ...
Rischi
• Il test può fallire per due o più comportamenti diversi
• Cosa vogliamo veramente testare?
Suggerimenti
• Una sola azione per test
• Evitare act -> assert, act -> assert …
• Sfruttare questo smell per spingere il ...
Arrange
ATTENZIONE: se il test richiede un setup
complesso c’è quasi certamente un problema
con il design del codice di produzione...
@Test(expected = MissingTitleException.class)
public void missingTitle()
{
new Book(1,
“123485”,
null,
“an adventure book”...
Rischi
• Quali dati sono importanti per ogni test?
• Se domani il codice ISBN deve essere di almeno 14 caratteri, quanti
t...
Suggerimento
• Evitate dettagli inutili per il test
• Rendono più complicato capire cosa è importante per il test
• Rischi...
Object Mother
• Classe con delle fixture pre-impostate
• Esempi:
• BookFixture.newBookWithMissingTitle ( )
• BookFixture.ne...
Rischio
• Le fixture possono aumentare in maniera poco controllabile
• Questa soluzione potrebbe non scalare abbastanza
Builder
• Una classe che permette di generare un oggetto con uno stato
preciso, partendo da valori di default sensibili
public class BookBuilder
{
private long id = 1;
private String title = “A BOOK TITLE”;
private String isbn = “123466579490...
@Test(expected = MissingTitleException.class)
public void missingTitle()
{
aBook().withTitle(null).build();
}
@Test(expect...
Vantaggi
• Ridotta duplicazione
• i valori di default sono impostati in un unico punto
• Aumentato l’espressività del test...
Metodi privati nella
classe di test
@Test
public void obsoleteFlight()
{
givenAnObsoleteFlight();
whenITryToBookIt();
thenIGetAnError()
}
Vantaggi
• l’espressività del comportamento è massima
Rischi
• fatico ad ottenere feedback dal design
• posso nascondere d...
Suggerimento
• usate i metodi privati nei test con attenzione
• ogni metodo privato deve essere di 1 o 2 righe
• lo scopo ...
Stato globale
@Test
public void creationDate()
{
Date currentDate = new Date();
Book book = new Book();
assertThat(book.creationDate(), ...
Suggerimenti
• Evitare il più possibile gli stati globali (ad esempio il tempo)
• È possibile iniettare un collaboratore c...
E quando abbiamo diversi
messaggi tra diversi oggetti?
Incoming | Outgoing
Object Under Test
Incoming
Outgoing
Command Query Separation
• Query:
• un messaggio che torna qualcosa
• non ha side-effect
• Comando
• non torna nulla (void)...
Test Object Under Test
Incoming Query Message
query message
Inviamo un messaggio dal test al oggetto sotto test
response
O...
Test Object Under Test
Incoming Command Message
command message
Inviamo un messaggio dal test al oggetto sotto test
Oggett...
@Test
public void soldBook()
{
Book book = new Book();
book.sell();
assertFalse(“book cannot be sold again”, book.canBeSol...
Test Object Under Testresponse
Outgoing Query Message
query message
Inviamo un messaggio dal test al oggetto sotto test
Ve...
Integration test?
Quando usare un test double?
• Valore: MAI !
• Entità: A volte
• Servizio: Sempre
Test Object Under Test
query message
response
Outgoing Query Message
Non facciamo asserzioni sul messaggio di query tra Ob...
@Test
public void listManyBooks()
{
BookRepository bookRepository = context.mock(BookRepository.class);
ListBooksUseCase =...
Test Object Under Test
Outgoing Command Message
Inviamo un messaggio dal test al oggetto sotto test
Dobbiamo verificare che...
Test Object Under Test
command message
Outgoing Command Message
Collaborator
MOCK
Impostiamo una expectation sul fatto che...
@Test
public void productFound()
{
ProductRepository productRepository =
context.mock(ProductRepository.class);
Display di...
Test Object Under Test
Message sent to self (private)
Non facciamo nessuna verifica sul messaggio privato
Perché usare i mock (test double)?
• Se ben usati generano enorme pressione sul design
• Vanno usati come strumento di des...
In brevissimo
• Curate i vostri test come curate il codice di produzione
• Fate code-review anche dei test!
• Un buon test...
Thank you!
Domande?
Upcoming SlideShare
Loading in …5
×

How to survive your unit tests

909 views

Published on

Finalmente un nuovo progetto, questa volta siamo responsabili: lo facciamo in TDD.
Pronti, via...si parte.
Test dopo test, refactoring dopo refactoring abbiamo le prime funzionalità in produzione, il cliente è contento, tutto va per il meglio....
... Fin quando arriva la richiesta inaspettata del cliente. Ma noi siamo agili, abbracciamo il cambiamento.
Dobbiamo rivedere parte del design per poter calare in maniera naturale la nuova funzionalità nel codice, ma ci accorgiamo che la nostra amata suite di test ci tiene in ostaggio. Ogni piccola modifica fa fallire tanti, troppi test.
Come sopravvivere? Come scrivere test che ci saranno veramente utili nel proseguimento del progetto?
Durante il talk vedremo alcune semplici linee guida che ci possono aiutare a rendere la nostra suite amica, anche durante i refactoring più estremi!

Published in: Engineering

How to survive your unit tests

  1. 1. How to survive your unit tests @racingDeveloper - Gabriele Tondi
  2. 2. Gabriele Tondi @racingDeveloper Agile Software Developer Coordinatore: XPUG-MI Appassionato di: sviluppo software, metodologie agili (eXtreme Programming), OOD, TDD e motori
  3. 3. Story time
  4. 4. Greenfield Project
  5. 5. Test Driven Development
  6. 6. First Iteration
  7. 7. Product Owner Status
  8. 8. Developers Status
  9. 9. Nuova Funzionalità
  10. 10. Embrace Change
  11. 11. Cambiamento nel Design
  12. 12. Un piccolo cambiamento, tanti test rotti
  13. 13. Come sopravvivere?
  14. 14. 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
  15. 15. Perché i nostri test si oppongono al refactoring?
  16. 16. 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
  17. 17. ATTENZIONE: il più delle volte un test è difficile da scrivere a causa di un problema nel design del codice di produzione. !
  18. 18. Focus di oggi è sul design dei test realizzati durante le iterazioni TDD
  19. 19. Semplici linee guida. L’insieme fa la differenza.
  20. 20. 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
  21. 21. Naming
  22. 22. @Test public void testThatValuesLowerThan3AreInvalid() { assertFalse(validator.validate(2)); }
  23. 23. @Test public void testThatValuesLowerThan3AreInvalid() { assertFalse(validator.validate(2)); } Evitare rumore nel nome dei test Evitare duplicazione nel nome dei test Lasciamo che sia il test stesso a dirci cosa succede Suggerimento: identificare scenari con i nomi dei test Esempi: tooLowValue, notANumber
  24. 24. @Test public void tooLowValue() { assertFalse(validator.validate(2)); }
  25. 25. Assert
  26. 26. @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)
  27. 27. WTF ?!?!?
  28. 28. @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>
  29. 29. Guardate il test fallire! … sempre! Anche se per qualche motivo già passa.
  30. 30. Suggerimenti • Usate asserzioni specifiche • Se non fosse possibile, includete un messaggio • assertFalse(“value is invalid”, validator.validate(…)) • Fate particolare attenzione ai custom matcher! (Hamcrest)
  31. 31. Act
  32. 32. @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()); }
  33. 33. Rischi • Il test può fallire per due o più comportamenti diversi • Cosa vogliamo veramente testare?
  34. 34. 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?
  35. 35. Arrange
  36. 36. ATTENZIONE: se il test richiede un setup complesso c’è quasi certamente un problema con il design del codice di produzione. !
  37. 37. @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() // […]
  38. 38. Rischi • Quali dati sono importanti per ogni test? • Se domani il codice ISBN deve essere di almeno 14 caratteri, quanti test devo cambiare?
  39. 39. 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
  40. 40. Object Mother • Classe con delle fixture pre-impostate • Esempi: • BookFixture.newBookWithMissingTitle ( ) • BookFixture.newBookWithMissingISBN ( ) • BookFixture.newBookWithMissingID ( )
  41. 41. Rischio • Le fixture possono aumentare in maniera poco controllabile • Questa soluzione potrebbe non scalare abbastanza
  42. 42. Builder • Una classe che permette di generare un oggetto con uno stato preciso, partendo da valori di default sensibili
  43. 43. 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); } }
  44. 44. @Test(expected = MissingTitleException.class) public void missingTitle() { aBook().withTitle(null).build(); } @Test(expected = MissingISBNException.class) public void missingISBN() { aBook().withISBN(null).build(); }
  45. 45. 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
  46. 46. Metodi privati nella classe di test
  47. 47. @Test public void obsoleteFlight() { givenAnObsoleteFlight(); whenITryToBookIt(); thenIGetAnError() }
  48. 48. 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!
  49. 49. 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
  50. 50. Stato globale
  51. 51. @Test public void creationDate() { Date currentDate = new Date(); Book book = new Book(); assertThat(book.creationDate(), greaterThan(currentDate)); }
  52. 52. 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!
  53. 53. E quando abbiamo diversi messaggi tra diversi oggetti?
  54. 54. Incoming | Outgoing Object Under Test Incoming Outgoing
  55. 55. Command Query Separation • Query: • un messaggio che torna qualcosa • non ha side-effect • Comando • non torna nulla (void) • ha dei side-effect
  56. 56. 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
  57. 57. 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
  58. 58. @Test public void soldBook() { Book book = new Book(); book.sell(); assertFalse(“book cannot be sold again”, book.canBeSold()); } Esempio
  59. 59. 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
  60. 60. Integration test?
  61. 61. Quando usare un test double? • Valore: MAI ! • Entità: A volte • Servizio: Sempre
  62. 62. Test Object Under Test query message response 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
  63. 63. @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
  64. 64. 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 Collaborator command message Oggetto sotto test invia un comando al collaboratore
  65. 65. Test Object Under Test command message Outgoing Command Message Collaborator MOCK Impostiamo una expectation sul fatto che il messaggio sia inviato
  66. 66. @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
  67. 67. Test Object Under Test Message sent to self (private) Non facciamo nessuna verifica sul messaggio privato
  68. 68. 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
  69. 69. 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
  70. 70. Thank you!
  71. 71. Domande?

×