I am practicing TDD for some years now and it took me a while and some mistakes to fully take advantage of it. Unfortunately I see a lot of people struggling with TDD and making the same mistakes I have made in the past. So here is a summary of what I think are the key aspects of TDD:
1. Start with the acceptance tests for your feature.
This is the most important one of all. According to its definition “In TDD each new feature starts with writing a test”[1]. Since a feature is always something that the user wants you must, by definition, start with the tests for your user stories. Now-a-days that is called Acceptance Test-Driven Development, but that is just toying with words. It is just TDD as it is meant to be IMHO.
A good way to write your tests is to use examples in BDD style: Given, When, Then. Since examples are concrete they are usually understandable for both User and Programmer. I tend to use FitNesse as framework to automate these tests right away. Here is an excellent video tutorial by Bob Martin describing how to create BDD style tests in FitNesse.
2. Refactoring is part of the job.
I see a lot of Red-Green, Red-Green, Red-Green out there. Refactoring is actually the biggest step in the whole TDD cycle IMHO. This is where you do the actual design of your system. You created a test, made it work, now it is time to take a step back and make it right. Maybe this requires a (small) design session in front of a whiteboard with your colleagues, or maybe you can do it on a napkin. It doesn’t really matter how you do it, as long as you do it. Especially in the first couple of iterations of your project the refactoring step can be quite long in my experience. That is not so hard to grasp since the foundations of your system are created then.
TDD is about helping you to come to a good design for your system. Since the tests are in fact the first clients of your system they help you focus on what your system should do, and how it will be used.
3. Formulate your tests in high-level terms.
I got this tip on the FitNesse maillinglist from Rick Mugridge. This was a real A-ha erlebnis for me. It means that you avoid using implementation details in your tests. For instance if you are going to test a UI, the “wrong way” to define your test is:
open page myapp.com type "Lars" into field "Name" type "secret" into field "password" click button "login"
The “right way” is:
given a logged in user "Lars" with password "secret"
Why is this important?
First of all the “wrong way” example is not refactor safe. What if we change the name of the fields, or split it into 2 pages? Then that would require to change our tests. And one of the biggest advantages of (acceptance) tests is that you don’t need to change them when you do (small) refactorings.
Secondly when you define your tests the “right way” they can be used for both UI testing, API testing, SQL testing etc. Since the tests do not expose implementation details, we can use the definition for multiple implementations. Using FitNesse scenario’s you could simply reuse your test page and just use a different Fixture that implements the actual test in the preferred technology (SQL, Java, Webservice, Selenium etc). In my next blog I’ll show you how to do that.
4. Keep the database out as long as possible.
When designing your system with TDD you will do a lot of refactorings, especially in the first iterations. I you use a database from scratch that will only slow down the process since databases aren’t as refactorable as for instance Java code. Secondly in 99% of the applications the database is only a datastorage and the performance impact is not really an issue, meaning the database won’t impact your design in order to meet those performance criteria. So if it does not contribute to your design why drag it with you all the time? So in the first iterations I tend to use a hashmap or so as database implementation. Now-a-days it is relatively simple to map your object model to a relation model anyway using tools as Hibernate.
So to take full advantage of TDD start out with your User Story tests and define them on a high level (using Given, When, Then). When you made it work, refactor it to a good design. It helps when you keep the database out as long as possible, because databases slow down the refactoring process.
Lars
[1] http://en.wikipedia.org/wiki/Test-driven_development#1._Add_a_test

I totally agree with the first three statements. I have the same experience. The last one, however, is a self fulfilling prophecy: “Secondly in 99% of the applications the database is only a datastorage”, is going to be true if you keep the database out as long as possible. Because you do the design in your refactoring.
I think you should not keep any aspect out, and keep your options open: you can use a GPL like Java, or DSL like Drools or a PL/SQL. Also configuration and storage should be part of it. The requirements and experience of your developers and administrators determine the best solution, not just the developers.
TDD is not the same as Java programming, or is it?
[...] TDD Tips » In a previous blog on TDD I advocated that you should define your test on a high level. [...]