Jim and his colleagues (hope you don’t mind me using you as an example) write documentation as the reference point to code. Then, one group of people write the tests, and one group of people implement the functionality, both working from the same set of documents. They then run the functionality against the tests to see if they’ve done it correctly (or if perhaps the documentation is wrong).
(If the documentation is wrong, then documentation, test and code must all need rewriting. Do you have to wait for the documentation to be produced before the two teams of testers and coders can get on with the next bit?)
With Test Driven Design you write tests first, thinking all the time of the behaviour you want to achieve, then you write the functionality to pass the tests. So it doesn’t matter who writes the tests and who writes the functionality (and pairing helps you to avoid any bad design decisions that might otherwise get made).
The tests form the reference point. Because the full test suite is run every time anyone changes the build (about once an hour) you know very quickly if something is broken. Then, the person who breaks the build can look at the ‘broken’ code and tests and decide whether they’ve actually caused a bug, or whether the code should behave in a different way to the way it’s defined in the tests (usually this happens when a customer decides to change the requirements, or a bug is found because no one thought that a user would ever do something that obviously silly, even though they always do). The story cards provided by the customer also form a reference point, but if you don’t have a story card in your hand which directly contradicts the code behaviour, you know that the stories from previous iterations are still valid and still working.
When a requirement changes, or a bug is found, we write tests which fail, to prove that the software no longer does what we want it to. Then we change the code so that the tests pass again. There’s an excellent form of pair TDD called “Ping-pong programming” in which coder A writes a failing test which coder B must make pass, then coder B writes a failing test for coder A to implement, etc. The two coders sit together to do this. It’s a lot of fun and results in code that does as little as it possibly can to get the job done (ie: minimalist, clean, and very legible if me* and people like me** have beaten them over the head often enough).
Documentation makes it harder to change code, and is one of the reasons for the exponential cost-of-change curve seen on top-down projects. If your code is defined by its tests, then you can do whatever you like to them – refactor, rename, remove – and know that you haven’t broken any existing functionality. Five years down the line, the tests will tell you what the code ought to do, as well as giving you confidence that it still does it.
Oh, yeah – I think I’ve written a single one-line comment this week, and no documentation. You can tell what my code does because my tests say things like “testShouldAllowSheepToGrazeInTheField” and “testShouldPreventSheepFromWanderingOutsideField”. There are parsers around which will go through tests like this and produce documentation for you, eg:
– should allow sheep to graze in the field
– should prevent sheep from wandering outside field
Class ElectricFence*** extends Fence:
– should dissuade sheep from breaking this fence
* Should be “I and people like me”. Agile and English have this in common: it takes real effort to get it right, even the strictest proponents aren’t perfect, and when you do get it right, it can feel very strange.
** By people like me I mean people who have been beaten over the head regarding code legibility by people like me, repeatedly, until they got it. Like me.
**My ElectricFenceTest class extends the FenceTest class, so ElectricFence still maintains the behaviour of a Fence. Nothing to teach about TDD there; I just love geeky things like that.
Jim and his colleagues (hope you don’t mind me using you as an example) write documentation as the reference point to code. Then, one group of people write the tests, and one group of people implement the functionality, both working from the same set of documents.
What actually happened was a spec was made, people start to code it and as they code it they document as they go. Once part of the design is ‘finished’ its docs can then be handed over to the test team. They write tests based on the docs and you then see if the code/docs are right. If not then yes, you’ll have to change some things.
The tests form the reference point.
And if the tests are wrong? I imagine you have to go and rewite all of the code? It doesn’t matter which is done first – if one is wrong it will affect the other.
Documentation makes it harder to change code, and is one of the reasons for the exponential cost-of-change curve seen on top-down projects.
It doesn’t make it harder to change code. You can change it all you like as long as you update the docs. These could be Javadocs that are embedded alongside the code itself, they may be Word docs, whatever, either way you have an overhead yes. Writing code is an overhead, employing people is an overhead, testing is an overhead. Not all overheads are bad.
Believe me, I don’t like writing docs. I’d rather be coding but in the methodologies I’ve used, docs are a necessity.
Five years down the line, the tests will tell you what the code ought to do, as well as giving you confidence that it still does it.
I have a problem with this statement. With no docs, no comments, I have to read through the tests and assume that the person who wrote them got them right. Sure with docs you have to assume the docs are correct but you have the code too – you have more information upon which to base that decision. You don’t have to guess the intent of the function – good docs will tell you what it is.
I write a lot of comments in my code, a lot. I have been told that my code is some of the most legible and easy to maintain that some people have ever come across. I cannot imagine NOT writing comments.
Example of a test being wrong (or at least, incomplete) coming up.
I can’t imagine never writing comments, certainly. Sometimes code doesn’t comment itself; if there’s some strange reason why something needs to be done, then a comment is required. eg:
// Allows the DB connection to be stubbed out for tests.
I would never, ever ban comments. They’re pretty easy to maintain. Documents which aren’t included in the code, though, are a higher overhead. Even our Wiki, one of the most easily updated forms of documentation, is far out of date. Information which was relevant three months ago is now either common knowledge or simply wrong.
I don’t like comments which merely repeat the names of methods, classes or variables; and I do like these to have names that describe exactly what they are or do. In such cases there’s really no need for documentation beyond that contained in the code itself.
The real trick for me is in getting everyone to write self-documenting code!
I’ve also been told that my code is legible and easy to maintain (once, I seem to remember, by you!). I think if you were to read the code I write now, you’d still have that opinion, even though the comments are minimal. It takes a bit of work to name things appropriately, and certainly, if I had a bunch of junior developers who were reeling under the impact of all the Agile changes being made to their team, I’d let them continue to put comments in as and where they chose to. I no longer feel the need to comment everything myself. I hope that if my code is ever illegible, someone will come back and ask me, “Why did you do that?” And then I’ll take a second look at what I’ve been doing. It hasn’t happened yet, even though I’ve been on a different team for over a month.
People have asked me procedural questions – “How do I write a functional test in Abbot? Have you got a copy of the VM parameters used to log queries?” – and they’ve always come up and asked me in pairs. That way, even if I disappear and someone else goes too, someone somewhere still has the knowledge.
As for confidence in the tests / code being right, you don’t have to assume that the person who wrote them got them right. You assume that a combination of two developers working together, seeking feedback from an on-site customer, with weekly iterations tested by someone in QA (manual acceptance tests, rather than unit or automated functional tests), ensure that they’re right.