One thing I want to add about TDD that always made it impossible for me to build is how pointless (to me) it is to do TDD in the functional/integration areas.
I tweak shit loads of forms, but as soon as a form get a little bit complex, it's impossible, for me, to apprehend the parameter's structure. Same goes with views. Your first view implementation is likely to be very different than the one you end up using.
TDD is built on the assumption that you know everything from the get go which has never be the case for me.
I write tests when things are settle down and ready to go live as a mean for me to say: "Ok, I think this is good enough, let's go live with this, but beforehand, let's make sure it works as expected".
Then bugs appears, and I add tests to cover those cases so I don't introduce regressions in the future.
This shouldn't need to be the case with TDD. TDD is a tool for evolving the design & functionality. The whole reason for using it is that you don't know what you want beforehand.
With TDD you write a failing test, write the smallest amount of code necessary to make it pass, then refactor. After that you add another test and go round again. You can use this to drive your design for low level unit/model tests.
When writing higher level acceptance tests I like to write the tests in using the language you share with the customer [1]. If you do this then they should remain valid unless the requirements change, they should not be dependent on your understanding of the requirement. To achieve this you can have your tests manipulate a domain model. If you are not testing your model then you need a model for your tests. e.g. something like the page object pattern for webdriver[0].
This means you can sketch out your high level tests rapidly with a stub model, and then implement the mapping to your implementation once you have got the implementation more stable.
Adding a layer of indirection to the implementation like this also means you can refactor your implementation as much as you like without touching the test itself. You may need to modify the mapping between the language your tests use and the implementation as you change the implementation. This sort of test can be valid across major re-writes of the implementation. The tests should also be less brittle. If you change something that several tests interact with you should only need to update one place to fix it.
For integration tests I like to use the same approach, except now the language used relates to the common concepts across the two systems you are testing integration of. The concepts used in the APIs.
TDD is a means to an end, not an end in itself. I think some TDDers lose sight of that fact.
The end is well-tested code. Developers often lack the discipline to write tests after writing what appears to be working code. The point of TDD is to force that discipline by making it part of the process of writing the code in the first place.
It's a good technique that many use to great effect, but it is just that, a technique, not a goal in itself.
What matters is that, when you're checking in new code and pushing it upstream, that code has tests along with it. Whether you wrote the tests right before the check-in, or as part of a TDD cycle, is immaterial to everyone else.
I would argue the "end" of TDD is working, well factored code. It's a technique for continually getting rapid feedback on every code change that you make, working in very small iterations.
The fact it results in a regression test suite is a happy side benefit (and actually, some of the tests generated are inevitably short lived).
There are other benefits of working in this style. e.g You should nearly always have code in a state that could be deployed to production; It gives a rhythm for pair programming; It encourages a certain coding style.
Same with me. I use "test first" with unit/model tests only. For the rest I first write the code, test in the browser and when I am happy I write a test to make sure I won't break the feature with future code changes.
Comments
One thing I want to add about TDD that always made it impossible for me to build is how pointless (to me) it is to do TDD in the functional/integration areas.
I tweak shit loads of forms, but as soon as a form get a little bit complex, it's impossible, for me, to apprehend the parameter's structure. Same goes with views. Your first view implementation is likely to be very different than the one you end up using.
TDD is built on the assumption that you know everything from the get go which has never be the case for me.
I write tests when things are settle down and ready to go live as a mean for me to say: "Ok, I think this is good enough, let's go live with this, but beforehand, let's make sure it works as expected".
Then bugs appears, and I add tests to cover those cases so I don't introduce regressions in the future.
This shouldn't need to be the case with TDD. TDD is a tool for evolving the design & functionality. The whole reason for using it is that you don't know what you want beforehand.
With TDD you write a failing test, write the smallest amount of code necessary to make it pass, then refactor. After that you add another test and go round again. You can use this to drive your design for low level unit/model tests.
When writing higher level acceptance tests I like to write the tests in using the language you share with the customer [1]. If you do this then they should remain valid unless the requirements change, they should not be dependent on your understanding of the requirement. To achieve this you can have your tests manipulate a domain model. If you are not testing your model then you need a model for your tests. e.g. something like the page object pattern for webdriver[0].
This means you can sketch out your high level tests rapidly with a stub model, and then implement the mapping to your implementation once you have got the implementation more stable.
Adding a layer of indirection to the implementation like this also means you can refactor your implementation as much as you like without touching the test itself. You may need to modify the mapping between the language your tests use and the implementation as you change the implementation. This sort of test can be valid across major re-writes of the implementation. The tests should also be less brittle. If you change something that several tests interact with you should only need to update one place to fix it.
For integration tests I like to use the same approach, except now the language used relates to the common concepts across the two systems you are testing integration of. The concepts used in the APIs.
[0] http://code.google.com/p/selenium/wiki/PageObjects [1] Ubiquitous Language http://martinfowler.com/bliki/UbiquitousLanguage.html
TDD is a means to an end, not an end in itself. I think some TDDers lose sight of that fact.
The end is well-tested code. Developers often lack the discipline to write tests after writing what appears to be working code. The point of TDD is to force that discipline by making it part of the process of writing the code in the first place.
It's a good technique that many use to great effect, but it is just that, a technique, not a goal in itself.
What matters is that, when you're checking in new code and pushing it upstream, that code has tests along with it. Whether you wrote the tests right before the check-in, or as part of a TDD cycle, is immaterial to everyone else.
I would argue the "end" of TDD is working, well factored code. It's a technique for continually getting rapid feedback on every code change that you make, working in very small iterations.
The fact it results in a regression test suite is a happy side benefit (and actually, some of the tests generated are inevitably short lived).
There are other benefits of working in this style. e.g You should nearly always have code in a state that could be deployed to production; It gives a rhythm for pair programming; It encourages a certain coding style.
Same with me. I use "test first" with unit/model tests only. For the rest I first write the code, test in the browser and when I am happy I write a test to make sure I won't break the feature with future code changes.