I have often written acceptance tests for code that has not yet been written (in fact, I wrote an article about it) but I have never written tests that will work with any number of implementations, each with their own architecture. I don't even know how to go about it, but that never stopped me before...
I started by following my own advice from my article (Grow Your Harness Naturally) about writing acceptance tests...
Each test should read like a set of instructions that you might give to an expert user over the phone.
Here's the first test, lifted straight from the rules and lightly java-ified to make it executable:
// 2.1.1 A game of tenpins consists of ten frames.
@Test
public void aGameOfTenPinsConsistsOfTenFrames() {
assertThat(numberOfFrames(), is(10));
}
A non-java-programming reader would probably struggle to write something like this but they should be able to read it OK.
This might be an ideal job for FIT but, since I don't know FIT, I'll do the best I can in JUnit and issue a call for a FIT expert to show us how it is done. With luck we'll get a few JBehaves and RSpecs and what-have-yous so we can compare and contrast.
[If you write your own acceptance tests, ping me at kevin at agitar dot com and I'll link to them here]
The test fixture itself is abstract,
public abstract class SingleGameScoring {
and contains a bunch of protected methods that allow my tests to interact with your implementation without influencing the design too much.
Here's the default implementation of numberOfFrames:
protected int numberOfFrames() {
notImplementedYet("numberOfFrames");
return -1;
}
I expect you to override those methods and adapt them to your implementation and, until you do, they will fail with 'not implemented yet'. I did this rather than make them abstract, so you don't have to implement a whole bunch of methods from the get-go before you've even had a chance to think about your design.
Some of the tests, like this one,
// If none of the standing pins are knocked
// down by the second delivery in the frame,
// the score sheet shall be marked with a (-).
@Test
public void ifNoPinsAreKnockedDownBySecondBallMarkWithDash() {
deliverBall(3);
deliverBall(0);
assertThat(secondBallInFrame(1), is("-"));
}
imply that you will be testing through the UI but they are flexible enough to let you test "just below the UI" too.
You can get my first set of tests from here but if you'd rather write your own, go ahead. I wanted to explore what customer-facing tests for a bowling scorer might look like and the exploration is at least as valuable as the final tests.
The tests use JUnit4 and the Hamcrest libraries. Hamcrest consists of the matcher/constraints extracted from JMock into a standalone library. Judging from the debate that rages every time the topic comes up on the TDD list, their use in unit tests is still highly controversial but I hope you'll agree that they read much better in customer-facing tests.
I am going to TDD myself a solution now. Brian Marick did an experiment where he used customer-facing tests to drive the design, but I prefer to just read the tests for inspiration and then use TDD to discover the right design for myself. Once I have a solution I will go back and run the customer tests to make sure I have understood the requirements correctly.
Next installment: my solution with a concrete implementation of the acceptance tests.
Posted by Kevin Lawrence at March 9, 2007 05:45 PM
TrackBack URL for this entry: