Skip to content

Test driven development, my way

20 February 2013

We all do it, or at least some of us do it, trying to write test cases for a good code coverage of all the possible solutions for a complex problem. Some of us even try to drive the design for programs through a test driven approach. That is something that I am not a fan of because I think that once you start
programming for a while and start doing test driven development, you are capable enough of overseeing a problem and knowing the general direction for a solution, so the test driven design in my opinion can lead to waste. Waste as in test cases that are not needed anymore and if those test cases are not cleaned out might even lead to misunderstandings. Mind you, most of the time that won’t happen of course, because good test driven developers will clean away the waste. But that is not the point that I would like to make today. Over the years that I have been doing test driven development, I have grown into a way of test driven development that I thought I would like to share, and have your input on to improve my way of doing test driven development.

I use test driven development mostly for testing decision trees and to pin down a problem. Using those tests a reader can quickly oversee what the specific method is supposed to do and how it should handle the border lines. So I use the test cases more as documentation for the intent of the code I create, than to drive the design. As I stated above, I am most of the time already on the path to a solution, and I just want to narrow down the border lines and see if the solution sticks. Most of the classes that I create usually have some decision trees in them or some complex logic that must be tested and this is how I am doing that :
– for each business logic problem or decision tree I create a test case
– the setup defines every aspect that is needed for the happy path
– the first test contains the assert for the happy path
– each next test case will then fiddle with one setting of the happy path and assert that the test then fails.

So putting this together this might look like the following. Note that you should not have the slightest idea what the class or the logic is about before hand, but hopefully, after having seen the tests, you are aware what it should do:

@Mock
ProductDao productDao;

@Before
public void setupTheHappyPath() {
  productSpec.setModelYear("2012");
  control.setMinimumDemonstratorModelYear("2005");
  productSpec.setStartDate(TableConstants.ZERO_DATE);
  productDTO.setProductState(ProductState.UNSOLD);
  Product product = new Product(productSpec);
  product.setDemonstratorAllowed("1");
  when(productDao.get(productSpec.getKey())).thenReturn(product);
}

@Test
public void should_be_allowed_based_on_transitions() {
  assertTrue(ProductState.UNSOLD.isTransferAllowed(Transition.DEMONSTRATOR));
  assertTrue(ProductState.ODRIN.isTransferAllowed(Transition.DEMONSTRATOR));
}

@Test
public void should_fail_based_on_transitions() {
  assertFalse(ProductState.SOLD.isTransferAllowed(Transition.DEMONSTRATOR));
  assertFalse(ProductState.DEMIN.isTransferAllowed(Transition.DEMONSTRATOR));
  assertFalse(ProductState.NOT_MY_UNIT.isTransferAllowed(Transition.DEMONSTRATOR));
}

@Test
public void should_be_the_happy_path() {
  assertTrue(productService.isDemonstrator(productDTO, control));
}

@Test
public void should_fail_for_transtion() {
  productDTO.setProductState(ProductState.SOLD);
  assertFalse(productService.isDemonstrator(productDTO, control));
}

@Test
public void should_fail_for_demonstrator_not_allowed() {
  Product product = new Product(productSpec.getKey());
  product.setDemonstratorAllowed("0");
  when(productDao.get(productSpec.getKey())).thenReturn(product);

  assertFalse(productService.isDemonstrator(productDTO, control));
}

@Test
public void should_fail_for_an_too_old_model() {
  productSpec.setModelYear("2005");
  assertFalse(productService.isDemonstrator(productDTO, control));
}

@Test
public void should_fail_for_a_startdate_that_is_set() {
  productSpec.setStartDate(new Date());
  assertFalse(productService.isDemonstrator(productDTO, control));
}

@Test
public void should_be_ok_for_an_inTransit_despite_startDate_that_is_set() {
  productSpec.setStockStatus(StockStatus.IN_TRANSIT);
  productSpec.setStartDate(new Date());
  assertTrue(productService.isDemonstrator(productDTO, control));
}

So I start out with the setup and defining all the settings that should apply for the happy path to take place. The first test is then the assert for the happy path. After that are two tests to see if the underlying state machine for this specific state is working as intended. These tests could also have been included in a separate test class for the state machine, but since the state machine is needed to drive the decision tree, I have included it in here.

Next are all the leafs of the decision tree that I would like to have tested. There are even more tests in the real class but they are more variations of the decision leafs and thus not as interesting for the rest of the code. Another weird thing that you might notice is the underscores that I use. Most of the time you would see a complete line camelCased, but I find that using underscores it is better to read. I would have preferred to use spock, since I can then write an even better description, but that is for a different post (but for the interested, be sure to check it out, it’s groovy based and very clear to read).

So when I look at the code, I think the goal of the isDemonstrator method seems clear. At least it does to me. Concluding here are the points that refer to the “my way” in the subject of this post:

  • Each decision tree in a method gets its own class testcase
  • Each test case starts out with the setup for the happy path
  • Each test only has one assert and as less fiddling with the data that is needed to make the assert work
  • Each leaf in the decision tree gets its own test method, describing as much as possible when that specific leaf is called
  • No camel casing of the test methods to improve readability
  • So what do you think? What should be improved? What is too much. Please share your thoughts for an interesting discussion.

From → java, testing

Leave a Comment

Leave a comment