September 29, 2010

...Learn TDD with Codemanship

Two Potentailly Interesting Kata Ideas & "Real-World" TDD

I had a very interesting day of pairing/coaching at the BBC yesterday, with two potentially interesting ideas for katas being presented, which I plan to work on when I get a spare moment.

Nic Ford suggested a kata for creating a program that manages bingo cards - i.e., populating the grids with numbers and scoring against called numbers. ("Two fat ladies, twelve" and all that sort of thing). This is a potentially interesting one, as it involves randomly generated numbers and can be tackled using mocks if you figure out how to separate the key concerns. We could get on with the rules for checking called numbers against bingo cards by deferring the implementation of the logic for generating the grids, using mocks to inject test grids with specific combinations of numbers.

Shalim Kahn wanted to work on a self-contained real-world problem of creating a fake in-memory file system for use in their testing, which turned out to be a dandy exercise in triangulation - an aspect of TDD many folk struggle with, as there's a real knack to it. This one was cool in that we were triangulating towards some kind of hashmap behaviour, but doing it quite strictly in small steps of generalisation. The obvious starting point (not necessarily the best - I still need to work it through a few times to know that) was to write a string to a filepath and read it back. Test one just required Shalim to return a hardcoded string. The next failing test was to write and read a different string, so the fake file system now had to remember the last string written to it. Then Shalim wrote two different strings in order, which required a queue to be able to read them back in order. At this point, the filepaths being specified could still be ignored. The next failing test would be to read them back in a different order, requiring the code to know which file contents related to which file paths (e.g., using a hashmap).

Another quite interesting aspect of the Fake File System kata could be that, because it's job is to act in the same way as a real file system, substitutability is crucial. We need to to have high confidence that the implementation of the actual file system wrapper behaves in exactly the same way as it's fake counterpart with which it shares not just an interface, but a set of contracts. There's a neat little pattern I use for testing for substitutability which goes like this:

1. Create a factory method for the object under test, and write your tests against a common abstraction (e.g., your own FileSystem interface)
2. Create a subclass for each implementation of that interface (or abstract class), which overrides the factory method to return the right concrete type of object

JUnit, for example, will pick up each set of tests every time it sees a subclass of the test abstraction, and will run the same tests against each instance of the concrete implementation (e.g., run the tests against a FakeFileSystem object and a real one).

One thing I hope to do soon is to run workshops on "real-world" TDD, as it's not immediately obvious how all these great practices would apply in your typical Java EE technology stack (e.g., JSF, Spring, Hibernate, Web Services, Message Queues etc) or to a Flash GUI application. Indeed, I think this might be a missing jigsaw piece from the portfolio of courses I currently offer through Codemanship. I'll be developing a workshop as soon as SC2010 is out of the way.

Questions like "in end-to-end TDD, where we typically work down a call-stack from UI to back-end, where does triangulation apply?" are very warranted. My own experience has been that in real-world TDD on multi-tier apps, my goal is not to deliver an application slice that has a hard-coded response into UAT. I want to deliver a completed variant of app behaviour.

For example, if the acceptance test I'm working on is about checking for invalid email addresses, I wouldn't deliver a hard-baked response like "if emailAddress == "Fooey" throw new InvalidEmailaddressException()" because the customer hasn't really asked me for a feature that only catches people entering "Fooey" in the email address field.

In implementing my EmailAddressValidator class, I might triangulate through a sequence of invalid email addresses that apply to this variant (e.g., email addresses that are not empty strings but don't contain "@"), and deliver a working, fully-fledged component specifically to handle the variant of behaviour the acceptance test is designed to illustrate for all invalid email addresses. If I were using regular expressions to validate the email address (which is quite likely), they lend themselves to triangulation very nicely, since your regex can be fleshed out one case at a time.

More on this soon.

Posted 10 years, 6 months ago on September 29, 2010