2. Agenda
• Intro
• What is TDD?
• Why to adopt it?
• Experience
• Test smells and patterns
• Summary
• Where to go next?
3. About the author
• Software developer
• Agile coach/mentor
• Open source contributor
• Leader of Polish Agile User Group
4. What is TDD?
• It seems everyone knows it nowadays.
• At least everyone talks about it.
• Well, why do we still have bad code?
5. Why to adopt it?
• Explore system behavior.
• Create a specification.
• Drive code design.
• Think about quality.
• Last, but not least – build executable and
automated tests.
6. Experience
• TDD classes on major universities in Krakow,
Poland since 2007.
• TDD workshops at various conferences in
Poland.
• TDD coaching and trainings at Sabre Holdings.
7. The Good, The Bad & The Ugly
aka what to do and what to avoid in your tests
8. What to test?
• Methods vs Test Cases = 1:1 relationship
• How to name a test method?
class Flights { class FlightsTest {
addFlight(...); testAddFlight(...);
getFlight(...); testGetFlight(...);
getFlightSeats(...); testGetFlightSeats(...);
getCheapestSeat(...); testGetCheapestSeat(...);
bookSeat(...); testBookSeat(...);
} }
9. What to test?
• Implicit constructor testing
@Test
public void testConstructor() {
Flight flight = new Flight();
assertEquals(...);
assertEquals(...);
}
@Test
public void testConstructor() {
Flight flight = new Flight();
assertNotNull(flight);
}
10. It’s about behavior
• ...so write examples:
– tell me what your system does NOT have yet?
– answer it in programming language
– answer giving an example
11. Coding by example
• User story:
– As a user I want to get a number of available seats for a
given flight
class FlightTest {
@Test
public void shouldReturnNumberOfAvailableSeats() {
// given
Flight flight = new Flight();
flight.addSeats(10);
// when
int numberOfSeats = flight.getNumberOfAvailableSeats();
// then
assertEquals(10, numberOfSeats);
}
}
15. Bloated fixture
• One fixture to rule them all
Test method 1
Fixture
Test method 2
Test method 3
Test method 4
16. Healthy fixture
• Extract only common parts to setup method
@Test
You want to avoid it.
public void testAvailableSeatsCount() throws {
assertEquals(0, f.getAvailableSeats());
f.addSeat(new Seat(1, 100));
assertEquals(1, f.getAvailableSeats());
}
• Minimize it
• Use creation methods where appropriate
18. Noisy fixture
// given
Flight flight1 = new Flight(1, new Market("KRK", "YVR"), new Date(), 50);
Flight flight2 = new Flight(2, new Market("KRK", "YVR"), new Date(), 30);
Flight flight3 = new Flight(3, new Market("HKG", "YVR"), new Date(), 60);
List<Flight> flights = new ArrayList<Flight>();
flights.add(flight1);
flights.add(flight2);
flights.add(flight3);
FlightManager flightManger = new FlightManager(flights);
// when
List<Flight> flights = flightManger .getFlightsFromOrigin("KRK");
// then
assertEquals(2, flights.size());
assertTrue(flights.contains(flight1));
assertTrue(flights.contains(flight2));
19. Noisy fixture refactoring
• Hide irrelevant data with creation method
// given
Flight flight1 = createFlightFromOrigin("KRK");
Flight flight2 = createFlightFromOrigin("KRK");
Flight flight3 = createFlightFromOrigin("HKG");
• Make it simple
List<Flight> flights = new ArrayList<Flight>();
flights.add(flight1);
flights.add(flight2);
flights.add(flight3);
FlightManager flightManger = new FlightManager(flights);
FlightManager flightManager = new FlightManager(asList(flight1, flight2, flight3));
20. Fluent builders
• Make code more readable
• Worth to try if too many creation methods
• Require time to build
Flight flight = new Flight(1, new Market("KRK", "YVR"), new Date());
Flight flight = buildFlight().number(1).between("KRK", "YVR").today().build();
21. Stable test data
• Golden rule:
– No matter how many times you run the test it
should always give the same results!
• Random test data
• Time & date tests can be tricky
22. One behavior per method
• If you have more than one statement in when,
you may need a new example.
• If you have assertions outside then, you may
need a new example.
23. One verification per test
• Use assertions only in then block
• Make them as expressive as possible
assertEquals(2, flights.size());
assertTrue(flights.contains(flight1));
assertTrue(flights.contains(flight2));
Custom assertion:
assertFlightsContainOnly(flights, flight1, flight2);
Hamcerst:
assertThat(flights, hasItems(flight1, flight2));
FEST-Assert:
assertThat(flights).containsOnly(flight1, flight2);
24. Simplify assertions
• You don’t want to guess what you are
expecting. It’s expectation anyway!
assertEquals((a + b + c) / 3, average);
• Loops are bad.
int pos = 0;
for (ToolGroup group: groups) {
assertTrue(pos <= group.getLocation());
pos = group.getLocation();
}
25. Simplify assertions
• Never use conditionals!
– You definately need a new example
– ...or your example is broken
26. Testing exceptions
• It seems fine:
@Test(expected=IllegalFlightNumberException.class)
public void shouldFailForWrongFlightNumber() throws Exception {
FlightManager flightManager = new FlightManager();
flightManager.addFlight(new Flight(10));
flightManager.addFlight(new Flight(15));
flightManager.getFlight(50);
}
• But what if new Flight() can throw same
exception?
28. How to make it work?
• Train your people
• Give them simple rules to adhere to (e.g. test
template!)
• Encourage pair programming
• ...or at least code reviews
• Do not listen to excuses
– I don’t have time to write tests!
29. Where to go next?
• Online:
– xunitpatterns.com
– behavior-driven.org
• Offline:
– „xUnit Test Patterns” by Gerard Meszaros
– „Growing Object-Oriented Software, Guided by
Tests” by Steve Freeman, Nat Pryce
– „Behavior Driven Development in Ruby with
RSpec” by David Chelimsky, Aslak Hellesoy