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 Slide


  2. View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  6. <
    /
    recap>

    View Slide

  7. Why this talk?

    View Slide

  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.

    View 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Slide

  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

    View Slide

  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

    View Slide

  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.

    View Slide

  34. 🤔

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. Test: No registrations

    View Slide

  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

    View 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 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 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 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 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 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 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 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 Slide

  51. @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 Slide

  52. @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 Slide

  53. @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 Slide

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

    View 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 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 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 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 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 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 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 Slide

  62. @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 Slide

  63. @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 Slide

  64. @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 Slide

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

    View Slide

  66. Behaviour testing

    View Slide

  67. Event
    Manager
    Event


    Dao
    Mailer

    View Slide

  68. Event
    Manager
    Event


    Dao
    Mailer
    System under test (SUT)

    View Slide

  69. Event
    Manager
    Event


    Dao
    Mailer
    Collaborators

    View Slide

  70. Collaborators
    • Real thing

    • Sorta real thing

    View Slide

  71. Sorta real thing
    • Stubs

    • Mocks

    • Fakes

    • Dummies

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

    View Slide

  72. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?

    View Slide

  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

    View Slide

  74. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?
    • Coordination logic

    • Who is talking to who?

    View Slide

  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

    View Slide

  76. Current EventManager design
    • Encapsulates business logic

    • Who should be noti
    fi
    ed?
    • Coordination logic

    • Who is talking to who?

    View Slide

  77. Testing the business logic

    View 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 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 Slide

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


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


    verifyNoMoreInteractions(mailer)

    View Slide

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


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


    verifyNoMoreInteractions(mailer)

    View Slide

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


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


    verifyNoMoreInteractions(mailer)

    View Slide

  83. Testing the coordination logic

    View Slide

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


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


    verifyNoMoreInteractions(mailer)

    View Slide

  85. Do-Over

    View Slide

  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)


    }


    }


    }

    View Slide

  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)


    }


    }


    }

    View Slide

  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)


    }


    }


    }

    View Slide

  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)


    }


    }


    }

    View Slide

  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)


    }


    }


    }

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

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


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

    View Slide

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

    View Slide

  99. Test: No registrations

    View 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 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 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 Slide

  103. @Test


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


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View Slide

  104. @Test


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


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View Slide

  105. @Test


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


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View Slide

  106. @Test


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


    //
    given


    val noRegistrants = emptyList()


    //
    when


    val potentialAttendees = noRegistrants.potentialAttendees()


    //
    then


    assertThat(potentialAttendees)


    .isEmpty()


    }

    View Slide

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

    View 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 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 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 Slide

  111. @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 Slide

  112. @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 Slide

  113. @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 Slide

  114. @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 Slide

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

    View Slide

  116. 🤨

    View Slide

  117. Value testing

    View Slide

  118. I O

    View Slide

  119. I O
    Input

    View Slide

  120. I O
    Output

    View Slide

  121. I O
    System under test (SUT)

    View Slide

  122. Collaborators
    • None

    • We use real things

    View Slide

  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

    View Slide

  124. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


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


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


    )

    View Slide

  125. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


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


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


    )

    View Slide

  126. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


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


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


    )

    View Slide

  127. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


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


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


    )

    View Slide

  128. Testing the business logic
    assertThat(potentialAttendees)


    .isEmpty()
    assertThat(potentialAttendees)


    .containsExactly(


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


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


    )

    View Slide

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

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

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

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

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

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

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

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

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

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

  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
    💯

    View Slide

  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
    💯

    View 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 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 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 Slide

  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
    💯

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View 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 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 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 Slide

  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

    View 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 Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

  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
    💯

    View Slide

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

    View Slide

  163. What did we learn?
    • Behaviour testing

    • Value testing

    • Micro-unit tests

    • Complementary tests

    • Integration tests

    View Slide

  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.

    View Slide

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

  166. Writing tests for code you wrote


    vs.


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

    View Slide

  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)


    }

    View Slide

  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)


    }

    View Slide

  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)


    }

    View Slide

  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]

    View Slide

  171. View Slide

  172. Refactor ✌

    View Slide

  173. View Slide

  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

    View Slide

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

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

    • https://github.com/approvals

    View Slide

  176. More testing tools & techniques
    • Performance testing

    • Property testing

    View Slide

  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

    View Slide

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

    View Slide