Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

Friday, February 13, 2009

On testing data objects, applied TDD

In a previous post I discussed how you might approach unit testing a data object. In a test-first approach to development, we first write the tests (obviously), and then write the simplest code that makes the test pass. In this exercise I will propose the tests, writing the code will be up to you. This exercise is designed to be written in Java with JUnit 3, but can be easily adapted to another xUnit framework and language.

The context of the exercise is the reservation of tables at a fair. A person can reserve one or more tables on which to present their offerings.

First lets imagine we want to create a reservation for a single table by 'bob'.

public void testSingleTableReservationBelongsToReserver() {
 Reservation bobsReservation = Reservation.forSingleTable("bob");
 assertEquals("bob", bobsReservation.getReservedBy());
}

Ask yourself what the simplest code to implement the reservation class would look like, and write it. Make sure your test passes.

Next we will validate that the reservation is specifically for one table. This is another independent test since I want to be able to easily identify the source of failure if a test fails. Chaining multiple assertions in a single test can cause one failure to hide another.

public void testSingleTableReservationReservesOneTable() {
 Reservation bobsReservation = Reservation.forSingleTable("bob");
 assertEquals(1, bobsReservation.getNumberOfTables());
}

Again, implement the simplest code to make the test pass. In this case the method getNumberOfTables could return the constant '1'.

Next let's validate that the cost of the reservation is the sum of the registration fee (30$) and the cost per table (20$). Again this method could return a hard-coded constant.

public void testCostOfSingleTableReservationIsCostPerTablePlusRegistrationFee();
 Reservation bobsReservation = Reservation.forSingleTable("bob");
 assertEquals(50, bobsReservation.getTotalReservationCost());
}

Next let's do a negative test for the creation of a multi-table reservation. The maximum number of tables allowed is 4 per person.

public void testCannotCreateReservationForMoreThanMaxAllowedTables() {
 try {
  Reservation.forMultipleTable("jim", 5);
  fail();
 } catch (TooManyTablesRequestedException a) {
 }
}

It seems that Jim's out of luck. This test forced us to create a new factory method as well as a new exception. If your new method has an if statement, you've already gone too far. At this point your method should only contain a single statement: a throw new exception. I know it's painfuly, small step to many newbies, but we are working our way up slowly as would be done in TDD. Its is an incremental development process.

Ok, maybe Jim can settle for reserving 4 tables.

public void testCreateReservationForMaximumNumberOfTables() throws Exception {
 Reservation joesReservation = Reservation.forMultipleTable("joe", 4);
 assertEquals(4, joesReservation.getNumberOfTables());
}

So far things are moving along well. Our business rules for the calculation of the cost of the reservation may need some elaborating. Lets ask for the cost of reserving two tables.

public void testCostOfReservationForTwo() throws Exception{
  Reservation chrisReservation = Reservation.forMultipleTable("chris", 2);
  assertEquals(70, chrisReservation.getTotalReservationCost());
}

At this point we may apply a 10% discount to Jim's reservation because he's reserving 4 tables.

public void testCostOfReservationForFourTablesIncludesDiscount() throws Exception{
  Reservation joesReservation = Reservation.forMultipleTable("joe", 4);
  assertEquals(99, joesReservation.getTotalReservationCost());
}

We will now add two tests that will target the updating of the data object's numberOfTables property.

public void testChangeNumberOfTablesReservedOverridesPreviousNumber() throws Exception {
  Reservation joesReservation = Reservation.forMultipleTable("joe", 4);
  joesReservation.setNumberOfTables(3);
  assertEquals(3, joesReservation.getNumberOfTables());
}
 
public void testChangeNumberOfTablesReservedChangesCostOfReservation() throws Exception {
  Reservation joesReservation = Reservation.forMultipleTable("joe", 4);
  joesReservation.setNumberOfTables(1);
  assertEquals(50, joesReservation.getTotalReservationCost());
}

In many baby steps we will have developed a simple, well tested implementation of Reservation. This particular exercise is trivial, and did not push us to do any complicated refactoring. In practice we would want to follow the tree step TDD cycle: red, green, refactor.

Usually the hardest part to test-first development is imagining what to test. I hope this exercise has shed some light on how to start the test writing.