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!

Outside In TDD

By Franziska Sauerwein

Outside In TDD

TDD (Test Driven Development) has become a widespread practice and CV buzzword in the development community. When I started getting involved in the European Software Craftsmanship Community, I thought I knew what it was - I was surprised to learn there are many variations of it, and two main schools: the classic approach and the London school. Three years later, and I have been using the outside-in approach successfully in multiple projects and combining it with the classic approach. This talk will give you an introduction with code examples using Java, Mockito and JUnit, illustrating the basic principles and when to choose it over other styles of TDD. I will tell from my experiences on how to choose and combine an appropriate TDD approach for typical situations when developing business software.

  • 7,619