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.

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

    View full-size slide

  2. 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

    View full-size slide

  3. 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

    View full-size slide

  4. Building Robust Software Playlist
    https://bit.ly/3F2ijgm

    View full-size slide

  5. Why this talk?

    View full-size slide

  6. 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.

    View full-size slide

  7. 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

    View full-size slide

  8. 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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  31. 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.

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  38. Test: No registrations

    View full-size slide

  39. 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

    View full-size slide

  40. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  41. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  42. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  43. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  44. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  45. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  46. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  47. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  48. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  49. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

  50. @Test


    fun `don't send emails when there are no registrants`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    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)


    }

    View full-size slide

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

    View full-size slide

  52. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  53. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  54. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  55. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  56. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  57. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  58. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  59. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  60. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  61. @Test


    fun `email only those who RSVP'd yes and maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

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

    View full-size slide

  63. Behaviour testing

    View full-size slide

  64. Event
    Manager
    Event


    Dao
    Mailer

    View full-size slide

  65. Event
    Manager
    Event


    Dao
    Mailer
    System under test (SUT)

    View full-size slide

  66. Event
    Manager
    Event


    Dao
    Mailer
    Collaborators

    View full-size slide

  67. Collaborators
    • Real thing

    • Sorta real thing

    View full-size slide

  68. Sorta real thing
    • Stubs

    • Mocks

    • Fakes

    • Dummies

    • Spies
    https://martinfowler.com/articles/mocksArentStubs.html

    View full-size slide

  69. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?

    View full-size slide

  70. 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

    View full-size slide

  71. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?
    • Coordination logic

    • Who is talking to who?

    View full-size slide

  72. 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

    View full-size slide

  73. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?
    • Coordination logic

    • Who is talking to who?

    View full-size slide

  74. Testing the business logic

    View full-size slide

  75. Testing the business logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  76. Testing the business logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  77. Testing the business logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  78. Testing the business logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  79. Testing the business logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  80. Testing the coordination logic

    View full-size slide

  81. Testing the coordination logic
    verifyNoInteractions(mailer)
    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)

    View full-size slide

  82. 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)


    }


    }


    }

    View full-size slide

  83. 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)


    }


    }


    }

    View full-size slide

  84. 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)


    }


    }


    }

    View full-size slide

  85. 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)


    }


    }


    }

    View full-size slide

  86. 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)


    }


    }


    }

    View full-size slide

  87. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  88. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  89. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  90. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  91. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  92. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

  93. Users.kt
    fun List.potentialAttendees(): List =


    this.filter { user
    ->
    user.rsvp
    =
    =
    Rsvp.YES
    |
    |
    user.rsvp
    ==
    Rsvp.MAYBE }

    View full-size slide

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

    View full-size slide

  95. Test: No registrations

    View full-size slide

  96. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  97. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  98. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  99. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  100. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  101. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

  102. @Test


    fun `no potential attendees for an empty list`() {


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View full-size slide

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

    View full-size slide

  104. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  105. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  106. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  107. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  108. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  109. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

  110. @Test


    fun `potential attendees contains only registrants those who RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }

    View full-size slide

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

    View full-size slide

  112. Value testing

    View full-size slide

  113. I O
    System under test (SUT)

    View full-size slide

  114. Collaborators
    • None

    • We use real things

    View full-size slide

  115. 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

    View full-size slide

  116. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )

    View full-size slide

  117. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )

    View full-size slide

  118. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )

    View full-size slide

  119. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )

    View full-size slide

  120. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )

    View full-size slide

  121. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  122. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  123. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  124. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  125. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  126. Testing the coordination logic
    @Test


    fun `query and email folks who RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }


    View full-size slide

  127. Implementation A vs. B
    @Test


    fun `RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }
    @Test


    fun `RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  128. Implementation A vs. B
    @Test


    fun `RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }
    @Test


    fun `RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  129. Implementation A vs. B
    @Test


    fun `RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }
    @Test


    fun `RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  130. Implementation A vs. B
    @Test


    fun `RSVP'd yes or maybe`() {


    //
    given


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    //
    when


    val potentialAttendees = allRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .containsExactly(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.MAYBE)


    )


    }
    @Test


    fun `RSVP'd yes and maybe to the event`() {


    //
    given


    val dao = mock()


    val mailer = mock()


    val eventManager = EventManager(dao, mailer)


    val event = Event(


    1,


    "Building Robust Software III",


    "Event is on 16th December, please attend."


    )


    val allRegistrants = listOf(


    User("[email protected]", Rsvp.YES),


    User("[email protected]", Rsvp.NO),


    User("[email protected]", Rsvp.MAYBE)


    )


    whenever(dao.getRegistrants(event.id)).thenReturn(allRegistrants)


    //
    when


    eventManager.sendReminder(event)


    //
    then


    verify(mailer).mail("[email protected]", event.reminder)


    verify(mailer).mail("[email protected]", event.reminder)


    verifyNoMoreInteractions(mailer)


    }

    View full-size slide

  131. 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
    💯

    View full-size slide

  132. 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
    💯

    View full-size slide

  133. 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
    💯

    View full-size slide

  134. 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
    💯

    View full-size slide

  135. 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
    💯

    View full-size slide

  136. 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
    💯

    View full-size slide

  137. 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

    View full-size slide

  138. 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

    View full-size slide

  139. 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

    View full-size slide

  140. 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

    View full-size slide

  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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  144. 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

    View full-size slide

  145. 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
    💯

    View full-size slide

  146. 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
    💯

    View full-size slide

  147. 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
    💯

    View full-size slide

  148. 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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  152. 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
    💯

    View full-size slide

  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
    💯

    View full-size slide

  154. Destroy All Software—Boundaries
    https://bit.ly/3yuuxvQ

    View full-size slide

  155. What did we learn?
    • Behaviour testing

    • Value testing

    • Micro-unit tests

    • Complementary tests

    • Integration tests

    View full-size slide

  156. 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.

    View full-size slide

  157. package com.gildedrose


    class GildedRose(var items: Array) {


    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


    }


    }


    }


    }


    }


    }

    View full-size slide

  158. Writing tests for code you wrote


    vs.


    Writing tests for someone else’s code
    (characterisation tests)

    View full-size slide

  159. @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)


    }

    View full-size slide

  160. @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)


    }

    View full-size slide

  161. @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)


    }

    View full-size slide

  162. [-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]

    View full-size slide

  163. Refactor ✌

    View full-size slide

  164. 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

    View full-size slide

  165. Approval Tests
    • https://github.com/approvals/ApprovalTests.Java

    • https://github.com/approvals/ApprovalTests.Plugins.IntelliJ

    • https://github.com/approvals

    View full-size slide

  166. More testing tools & techniques
    • Performance testing

    • Property testing

    View full-size slide

  167. 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

    View full-size slide

  168. Fin
    Questions?
    @ragunathjawahar • https://ragunath.xyz

    View full-size slide