Experiences in Outside-In TDD 

London from the Shard CC-BY DncnH

London from the Shard CC-BY DncnH

     Hi!

 

 

Juke

@Singsalad

 

Software Crafter

London from the Shard CC-BY DncnH

Test Driven Development

  1. Red: Write a failing test
  2. Green: Write just enough code to make it pass
  3. Refactor to improve design

Repeat

London from the Shard CC-BY DncnH

Classic approach

(aka Detroit School)

  • emergent design during refactoring
  • state-based tests
  • collaborators are usually not mocked

London from the Shard CC-BY DncnH

Example

public class RomanNumeralsShould {
  @Test public void convert_1_decimal_to_roman_numeral() {
    RomanNumeral romanNumeral = new RomanNumeral(1);

    assertThat(romanNumeral.toString(), is("I"));
  }

  @Test public void convert_2_decimal_to_roman_numeral() {
    RomanNumeral romanNumeral = new RomanNumeral(2);

    assertThat(romanNumeral.toString(), is("II"));
  }

  @Test public void convert_3_decimal_to_roman_numeral() {
    RomanNumeral romanNumeral = new RomanNumeral(3);

    assertThat(romanNumeral.toString(), is("III"));
  }
}

London from the Shard CC-BY DncnH

Benefits

  • easier to learn
  • avoids over-engineering

London from the Shard CC-BY DncnH

Drawbacks

  • state is sometimes exposed just for tests
  • collaborators emerge and develop life of their own - tests that use them break
  • emergent API might not fit

London from the Shard CC-BY DncnH

When to use

  • unknown implementation
  • known input and output
  • domain experts /domain language don't help (algorithms, data transformations)

London from the Shard CC-BY DncnH

From system entry point to single pieces of behaviour

  1. failing acceptance test verifying a feature
  2. failing unit test for the class handling the request

Outside-In approach

(aka London School)

London from the Shard CC-BY DncnH

  • assume collaborators & mock them
  • TDD collaborators 1:1, turning the acceptance test green
  • refine design

Next steps

London from the Shard CC-BY DncnH

Example

Feature request: 

"As a party host, I want to send a notification to all my friends about my upcoming event so that they save the date."

London from the Shard CC-BY DncnH

Talk to the business

Save the date feature: 

  • notification sender 
  • friend provider 
  • event plan
  • calendar

London from the Shard CC-BY DncnH

Start with an acceptance test

Given Ana is friends with Ben and Lara

And she has a birthday party on the 1st of March scheduled in the calendar

When she clicks on "Save the date"

Then A notification is sent to Ben and Lara with the date

London from the Shard CC-BY DncnH

Unit test EventPlan

public class EventPlanShould {
  @Test public void
  notify_friends_to_save_the_date() {
    given(friendProvider.getFriendsOf(ANA))
        .thenReturn(BEN, LARA);
    given(calendar.getTimeOf(BIRTHDAY_PARTY))
        .thenReturn(FIRST_OF_MARCH);

    eventPlan.saveTheDate(ANA, BIRTHDAY_PARTY);

    verify(notificationSender)
        .notify(BEN, BIRTHDAY_PARTY, FIRST_OF_MARCH);
    verify(notificationSender)
        .notify(LARA, BIRTHDAY_PARTY, FIRST_OF_MARCH);
  }}

London from the Shard CC-BY DncnH

Implement EventPlan

public class EventPlan { ...

  public void saveTheDate(User host, Event event) {
    
    Time timeOfEvent = calendar.getTimeOf(event);
    List<Friend> friends =   
                   friendProvider.getFriendsOf(host);
    friends.stream()
           .forEach(friend ->  
               notificationSender
                  .notify(friend,event,timeOfEvent));
  }}

London from the Shard CC-BY DncnH

TDD Calendar

public class CalendarShould {
  @Test public void provide_the_time_of_an_event() {
    calendar
      .addEvent(BIRTHDAY_PARTY, FIRST_OF_MARCH);
    
    Time timeOfEvent = 
         calendar.getTimeOf(BIRTHDAY_PARTY);
    
    assertThat(timeOfEvent, is(FIRST_OF_MARCH));
  }
}

London from the Shard CC-BY DncnH

TDD FriendProvider

public class FriendProviderShould {
  @Test public void provide_no_friends_of_a_user_without_friends() {
    List<Friend> friends = friendProvider.getFriendsOf(ANA);

    assertThat(friends,is(empty()));
  }
  
  @Test public void provide_a_single_friend_of_a_user() {
    friendProvider.befriend(ANA, BEN);

    List<Friend> friends = friendProvider.getFriendsOf(ANA);

    assertThat(friends,contains(BEN);
  }

  @Test public void provide_all_friends_of_a_user() {
    friendProvider.befriend(ANA, BEN);
    friendProvider.befriend(ANA, LARA);
   
    List<Friend> friends = friendProvider.getFriendsOf(ANA);
   
    assertThat(friends,contains(BEN,LARA);
  }}

London from the Shard CC-BY DncnH

And then...

  • TDD notification sender
  • see acceptance test turn green
  • refine design
  • write next acceptance test
  • repeat

London from the Shard CC-BY DncnH

  • Design in RED stage
  • verify behaviour, not state
  • use GREEN phase to refine
  • use domain language for naming

Key differences

London from the Shard CC-BY DncnH

Benefits

  • encapsulation / tell-don't-ask is easier to achieve
  • sticks to code satisfying the business needs
  • stays aligned to business process & language

London from the Shard CC-BY DncnH

Drawbacks

  • harder to master for TDD beginners
  • the tests don't help finding collaborators
  • needs acceptance criteria & understanding of feature

London from the Shard CC-BY DncnH

When & how to use

  • when starting a new feature
  • pair program!
  • discuss your design ideas and collaborators
  • work closely with domain expert

London from the Shard CC-BY DncnH

Which approach is better?

  • Neither!
  • Switch between the two, use them as appropriate
  • But practice and master both!

London from the Shard CC-BY DncnH

Further reading

London from the Shard CC-BY DncnH

Bank kata exercise

Create a simple bank application with the following features:

  • Deposit into Account
  • Withdraw from an Account
  • Print a bank statement to the console.

 

The statement should have the following format:

    DATE       | AMOUNT  | BALANCE
    10/04/2014 | 500.00  | 1400.00
    02/04/2014 | -100.00 | 900.00
    01/04/2014 | 1000.00 | 1000.00

London from the Shard CC-BY DncnH

Bank kata (2)

The entry point should be the following interface, which you can not change.

public class BankAccount {
    public void deposit(int amount);
    public void withdraw(int amount);
    public void printStatement();
}

Don't worry about introducing abstractions on Money or Date as this is not the point of the exercise.

 

Start with an acceptance test!