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
- Red: Write a failing test
- Green: Write just enough code to make it pass
- 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
- failing acceptance test verifying a feature
- 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