Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Robust Software, Episode 3

Building Robust Software, Episode 3

Presented at https://www.meetkt.org on 17 December 2021

This episode explains how the design of the production code can affect the maintenance of tests. We also touch upon different kinds of testing tools and methodologies suited for various software development and maintenance scenarios.

A8ee6ea52b89dfa1388b592a260c60a6?s=128

Ragunath Jawahar

December 17, 2021
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

  1. Ragunath Jawahar • @ragunathjawahar • https://ragunath.xyz Building Robust Software Episode

    3: Not going to convince you to write tests
  2. <recap>

  3. Episode 1: Small ideas, big impact • Robustness principle &

    a tweaked version of it • Algebraic data types • Total & partial functions • Minimising defensive programming • Using the type system to short-circuit failures (a.k.a failing fast) • Avoiding primitive obsession
  4. Episode 2: Exceptions, side-effects and boundaries • Exception anti-patterns •

    Handling • Throwing • Tester-Doer pattern • Try-Parse Pattern • Converting exceptions to values • Introduction to values as boundaries • Architectures that use values as boundaries
  5. Building Robust Software Playlist https://bit.ly/3F2ijgm

  6. < / recap>

  7. Why this talk?

  8. Scenario An event management startup has hired you and needs

    your help to build a feature that sends reminder emails to potential attendees for an upcoming event.
  9. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  10. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  11. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  12. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  13. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  14. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  15. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  16. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  17. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  18. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  19. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  20. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  21. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  22. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  23. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  24. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  25. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  26. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  27. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  28. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  29. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  30. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  31. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  32. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  33. Scenario Your company is losing money 💸 💸 💸 due

    to bug fi xes in production, so your team has decided to write tests. It's your responsibility to bring your modules under test.
  34. 🤔

  35. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  36. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  37. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  38. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  39. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  40. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  41. Test: No registrations

  42. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  43. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  44. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  45. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  46. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  47. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  48. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  49. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  50. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  51. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  52. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  53. @Test fun `don't send emails when there are no registrants`()

    { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) whenever(dao.getRegistrants(event.id)).thenReturn(emptyList()) val event = Event( "Building Robust Software III", "Event is on 16th December, please attend." ) // when eventManager.sendReminder(event) // then verifyNoInteractions(mailer) }
  54. Test: A mix of “Yes”, “No” and “Maybe”

  55. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  56. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  57. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  58. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  59. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  60. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  61. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  62. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  63. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  64. @Test fun `email only those who RSVP'd yes and maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  65. ✅ No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone

    RSVP’d “Maybe” ✅ A mix of “Yes”, “No” and “Maybe”
  66. Behaviour testing

  67. Event Manager Event Dao Mailer

  68. Event Manager Event Dao Mailer System under test (SUT)

  69. Event Manager Event Dao Mailer Collaborators

  70. Collaborators • Real thing • Sorta real thing

  71. Sorta real thing • Stubs • Mocks • Fakes •

    Dummies • Spies https://martinfowler.com/articles/mocksArentStubs.html
  72. Current EventManager design • Encapsulates business logic • Who should

    be noti fi ed?
  73. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  74. Current EventManager design • Encapsulates business logic • Who should

    be noti fi ed? • Coordination logic • Who is talking to who?
  75. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES || it.rsvp = = MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } a.EventManager.kt
  76. Current EventManager design • Encapsulates business logic • Who should

    be noti fi ed? • Coordination logic • Who is talking to who?
  77. Testing the business logic

  78. Testing the business logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  79. Testing the business logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  80. Testing the business logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  81. Testing the business logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  82. Testing the business logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  83. Testing the coordination logic

  84. Testing the coordination logic verifyNoInteractions(mailer) verify(mailer).mail("jane@example.com", event.reminder) verify(mailer).mail("joe@example.com", event.reminder) verifyNoMoreInteractions(mailer)

  85. Do-Over

  86. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .potentialAttendees() potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } class EventManager( private val dao: EventDao, private val mailer: Mailer ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES | | it.rsvp == MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } }
  87. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .potentialAttendees() potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } class EventManager( private val dao: EventDao, private val mailer: Mailer ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES | | it.rsvp == MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } }
  88. class EventManager( private val dao: EventDao, private val mailer: Mailer

    ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .potentialAttendees() potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } } class EventManager( private val dao: EventDao, private val mailer: Mailer ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .filter { it.rsvp == YES | | it.rsvp == MAYBE } potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } }
  89. b.EventManager.kt class EventManager( private val dao: EventDao, private val mailer:

    Mailer ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .potentialAttendees() potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } }
  90. b.EventManager.kt class EventManager( private val dao: EventDao, private val mailer:

    Mailer ) { fun sendReminder(event: Event) { val registrants = dao.getRegistrants(event.id) val potentialAttendees = registrants .potentialAttendees() potentialAttendees.onEach { attendee -> mailer.mail(attendee.emailAddress, event.reminder) } } }
  91. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  92. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  93. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  94. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  95. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  96. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  97. Users.kt fun List<User>.potentialAttendees(): List<User> = this.filter { user -> user.rsvp

    = = Rsvp.YES | | user.rsvp == Rsvp.MAYBE }
  98. No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone RSVP’d

    “Maybe” A mix of “Yes”, “No”, and “Maybe”
  99. Test: No registrations

  100. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  101. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  102. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  103. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  104. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  105. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  106. @Test fun `no potential attendees for an empty list`() {

    // given val noRegistrants = emptyList<User>() // when val potentialAttendees = noRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .isEmpty() }
  107. Test: A mix of “Yes”, “No” and “Maybe”

  108. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  109. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  110. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  111. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  112. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  113. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  114. @Test fun `potential attendees contains only registrants those who RSVP'd

    yes or maybe`() { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) }
  115. ✅ No registrations Everyone RSVP’d “Yes” Everyone RSVP’d “No” Everyone

    RSVP’d “Maybe” ✅ A mix of “Yes”, “No” and “Maybe”
  116. 🤨

  117. Value testing

  118. I O

  119. I O Input

  120. I O Output

  121. I O System under test (SUT)

  122. Collaborators • None • We use real things

  123. Newer EventManager design • Does not encapsulate business logic •

    Can use micro unit tests to test various cases • Value-in, value-out results in decoupled design • No dependencies for business logic
  124. Testing the business logic assertThat(potentialAttendees) .isEmpty() assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES),

    User("maybe@example.com", Rsvp.MAYBE) )
  125. Testing the business logic assertThat(potentialAttendees) .isEmpty() assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES),

    User("maybe@example.com", Rsvp.MAYBE) )
  126. Testing the business logic assertThat(potentialAttendees) .isEmpty() assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES),

    User("maybe@example.com", Rsvp.MAYBE) )
  127. Testing the business logic assertThat(potentialAttendees) .isEmpty() assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES),

    User("maybe@example.com", Rsvp.MAYBE) )
  128. Testing the business logic assertThat(potentialAttendees) .isEmpty() assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES),

    User("maybe@example.com", Rsvp.MAYBE) )
  129. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  130. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  131. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  132. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  133. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  134. Testing the coordination logic @Test fun `query and email folks

    who RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  135. Implementation A vs. B @Test fun `RSVP'd yes or maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) } @Test fun `RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  136. Implementation A vs. B @Test fun `RSVP'd yes or maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) } @Test fun `RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  137. Implementation A vs. B @Test fun `RSVP'd yes or maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) } @Test fun `RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  138. Implementation A vs. B @Test fun `RSVP'd yes or maybe`()

    { // given val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) // when val potentialAttendees = allRegistrants.potentialAttendees() // then assertThat(potentialAttendees) .containsExactly( User("yes@example.com", Rsvp.YES), User("maybe@example.com", Rsvp.MAYBE) ) } @Test fun `RSVP'd yes and maybe to the event`() { // given val dao = mock<EventDao>() val mailer = mock<Mailer>() val eventManager = EventManager(dao, mailer) val event = Event( 1, "Building Robust Software III", "Event is on 16th December, please attend." ) val allRegistrants = listOf( User("yes@example.com", Rsvp.YES), User("no@example.com", Rsvp.NO), User("maybe@example.com", Rsvp.MAYBE) ) whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants) // when eventManager.sendReminder(event) // then verify(mailer).mail("yes@example.com", event.reminder) verify(mailer).mail("maybe@example.com", event.reminder) verifyNoMoreInteractions(mailer) }
  139. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  140. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  141. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  142. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  143. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  144. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  145. Implementation A vs. B import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test import xyz.ragunath.events.Rsvp

    import xyz.ragunath.events.User import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import xyz.ragunath.events.Event import xyz.ragunath.events.EventDao import xyz.ragunath.events.Mailer import xyz.ragunath.events.Rsvp import xyz.ragunath.events.User
  146. Implementation A vs. B import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test import xyz.ragunath.events.Rsvp

    import xyz.ragunath.events.User import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import xyz.ragunath.events.Event import xyz.ragunath.events.EventDao import xyz.ragunath.events.Mailer import xyz.ragunath.events.Rsvp import xyz.ragunath.events.User
  147. Implementation A vs. B import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test import xyz.ragunath.events.Rsvp

    import xyz.ragunath.events.User import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import xyz.ragunath.events.Event import xyz.ragunath.events.EventDao import xyz.ragunath.events.Mailer import xyz.ragunath.events.Rsvp import xyz.ragunath.events.User
  148. Implementation A vs. B import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test import xyz.ragunath.events.Rsvp

    import xyz.ragunath.events.User import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import xyz.ragunath.events.Event import xyz.ragunath.events.EventDao import xyz.ragunath.events.Mailer import xyz.ragunath.events.Rsvp import xyz.ragunath.events.User
  149. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  150. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  151. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  152. Implementation A vs. B import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.Test import xyz.ragunath.events.Rsvp

    import xyz.ragunath.events.User import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import xyz.ragunath.events.Event import xyz.ragunath.events.EventDao import xyz.ragunath.events.Mailer import xyz.ragunath.events.Rsvp import xyz.ragunath.events.User
  153. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  154. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  155. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  156. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  157. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  158. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  159. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  160. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  161. Implementation A vs. B • 5 integration tests • +

    Dependencies • ↑ Coupling • Wider change propagation • Harder to maintain • More assertions • 5 micro-unit tests • 1 integration test • - Dependencies • ↓ Coupling • Limited change propagation • Easier to maintain • Fewer assertions 💯
  162. Destroy All Software—Boundaries https://bit.ly/3yuuxvQ

  163. What did we learn? • Behaviour testing • Value testing

    • Micro-unit tests • Complementary tests • Integration tests
  164. Scenario You inherit a codebase that does not have any

    tests, the previous developers have left the organisation and you are expected to enhance an existing feature.
  165. package com.gildedrose class GildedRose(var items: Array<Item>) { fun updateQuality() {

    for (i in items.indices) { if (items[i].name != "Aged Brie" && items[i].name ! = "Backstage passes to a TAFKAL80ETC concert") { if (items[i].quality > 0) { if (items[i].name != "Sulfuras, Hand of Ragnaros") { items[i].quality = items[i].quality - 1 } } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1 if (items[i].name == "Backstage passes to a TAFKAL80ETC concert") { if (items[i].sellIn < 11) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1 } } if (items[i].sellIn < 6) { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1 } } } } } if (items[i].name != "Sulfuras, Hand of Ragnaros") { items[i].sellIn = items[i].sellIn - 1 } if (items[i].sellIn < 0) { if (items[i].name != "Aged Brie") { if (items[i].name != "Backstage passes to a TAFKAL80ETC concert") { if (items[i].quality > 0) { if (items[i].name != "Sulfuras, Hand of Ragnaros") { items[i].quality = items[i].quality - 1 } } } else { items[i].quality = items[i].quality - items[i].quality } } else { if (items[i].quality < 50) { items[i].quality = items[i].quality + 1 } } } } } }
  166. Writing tests for code you wrote vs. Writing tests for

    someone else’s code (characterisation tests)
  167. @Test fun `aged brie`() { // given val itemName =

    "Aged Brie" val sellIns = arrayOf(-1, 0, 1, 5, 6, 7, 10, 11, 12) val qualities = arrayOf(-1, 0, 1, 49, 50, 51) // when val function = updateQualityFunction(itemName) // (Int, Int) - > String // then CombinationApprovals.verifyAllCombinations(function, sellIns, qualities) }
  168. @Test fun `aged brie`() { // given val itemName =

    "Aged Brie" val sellIns = arrayOf(-1, 0, 1, 5, 6, 7, 10, 11, 12) val qualities = arrayOf(-1, 0, 1, 49, 50, 51) // when val function = updateQualityFunction(itemName) // (Int, Int) - > String // then CombinationApprovals.verifyAllCombinations(function, sellIns, qualities) }
  169. @Test fun `aged brie`() { // given val itemName =

    "Aged Brie" val sellIns = arrayOf(-1, 0, 1, 5, 6, 7, 10, 11, 12) val qualities = arrayOf(-1, 0, 1, 49, 50, 51) // when val function = updateQualityFunction(itemName) // (Int, Int) - > String // then CombinationApprovals.verifyAllCombinations(function, sellIns, qualities) }
  170. [-1, -1] => [Aged Brie, -2, 1] [-1, 0] =>

    [Aged Brie, -2, 2] [-1, 1] => [Aged Brie, -2, 3] [-1, 49] => [Aged Brie, -2, 50] [-1, 50] => [Aged Brie, -2, 50] [-1, 51] => [Aged Brie, -2, 51] [0, -1] => [Aged Brie, -1, 1] [0, 0] => [Aged Brie, -1, 2] [0, 1] => [Aged Brie, -1, 3] [0, 49] => [Aged Brie, -1, 50] [0, 50] => [Aged Brie, -1, 50] [0, 51] => [Aged Brie, -1, 51] [1, -1] => [Aged Brie, 0, 0] [1, 0] => [Aged Brie, 0, 1] [1, 1] => [Aged Brie, 0, 2] [1, 49] => [Aged Brie, 0, 50] [1, 50] => [Aged Brie, 0, 50] [1, 51] => [Aged Brie, 0, 51] [5, -1] => [Aged Brie, 4, 0] [5, 0] => [Aged Brie, 4, 1] [5, 1] => [Aged Brie, 4, 2] [5, 49] => [Aged Brie, 4, 50] [5, 50] => [Aged Brie, 4, 50] [5, 51] => [Aged Brie, 4, 51] [6, -1] => [Aged Brie, 5, 0] [6, 0] => [Aged Brie, 5, 1] [6, 1] => [Aged Brie, 5, 2] [6, 49] => [Aged Brie, 5, 50] [6, 50] => [Aged Brie, 5, 50] [6, 51] => [Aged Brie, 5, 51] [7, -1] => [Aged Brie, 6, 0] [7, 0] => [Aged Brie, 6, 1] [7, 1] => [Aged Brie, 6, 2] [7, 49] => [Aged Brie, 6, 50] [7, 50] => [Aged Brie, 6, 50] [7, 51] => [Aged Brie, 6, 51] [10, -1] => [Aged Brie, 9, 0] [10, 0] => [Aged Brie, 9, 1] [10, 1] => [Aged Brie, 9, 2] [10, 49] => [Aged Brie, 9, 50] [10, 50] => [Aged Brie, 9, 50] [10, 51] => [Aged Brie, 9, 51] [11, -1] => [Aged Brie, 10, 0] [11, 0] => [Aged Brie, 10, 1] [11, 1] => [Aged Brie, 10, 2] [11, 49] => [Aged Brie, 10, 50] [11, 50] => [Aged Brie, 10, 50] [11, 51] => [Aged Brie, 10, 51] [12, -1] => [Aged Brie, 11, 0] [12, 0] => [Aged Brie, 11, 1] [12, 1] => [Aged Brie, 11, 2] [12, 49] => [Aged Brie, 11, 50] [12, 50] => [Aged Brie, 11, 50] [12, 51] => [Aged Brie, 11, 51]
  171. None
  172. Refactor ✌

  173. None
  174. Approval Tests • Snapshot testing, golden master testing, etc., •

    Helps bring large portions of code under tests quickly • Can work on di ff erent kinds of data
  175. Approval Tests • https://github.com/approvals/ApprovalTests.Java • https://github.com/approvals/ApprovalTests.Plugins.IntelliJ • https://github.com/approvals

  176. More testing tools & techniques • Performance testing • Property

    testing
  177. Workflows • TDD • https://www.coderetreat.org/ • TCR • https://medium.com/@kentbeck_7670/test-commit-revert-870bbd756864 •

    https://github.com/LarsEckart/tcr-extension • https://github.com/ragunathjawahar/GildedRose-Refactoring-Kata/tree/rj/ attempt-04
  178. Fin Questions? @ragunathjawahar • https://ragunath.xyz