I am not a fan of Test Driven Development, not at all.

Having your invariants and pre/post conditions correct is not enough. You also need to do the right thing. For example, you have a function that adds two durations in the form hh:mm:ss, you have mm <= 60 and ss <= 60 as an invariant, testing it is a good thing, but it won't tell you that your addition is correct. Imagine your result is always 1s too much, you can even test associativity and commutativity and it will pass. Having these correct is necessary but not sufficient.

Problem is, when you write tests first, especially tight, easy to run unit tests, you will be tempted to write code that pass the tests, not code that does the right thing. Like throwing stuff at your tests and see what sticks.

I much prefer the traditional approach of first solving the problem the best you can, which may of may not involve thinking about invariants, but always with the end result in mind. And only when you are reasonably confident with your code, you can start testing. If it fails, you will have the same temptation to just pass the test, but at least, you wrote it at least once without help from the tests.

Maybe that's just me, but when I tried the "tests first" approach, the end result got pretty bad.

You’ve missed the most important point of TDD—and indeed, of tests.

Tests do not ensure that your functions are correct; they ensure that you are alerted when their behavior changes.

Second, TDD is a way to force dogfooding: having to use the functions you’re going to write, before you write them, helps you design good interfaces.

> Tests do not ensure that your functions are correct; they ensure that you are alerted when their behavior changes.

I agree with that part and I am not against tests, just the idea of writing tests first.

> helps you design good interfaces

I am sure plenty of people will disagree but I think testability is overrated and leads to code that is too abstract and complicated. Writing tests first will help you write code that is testable, but everything is a compromise, and to make code more testable, you have to sacrifice something, usually in the form of adding complexity and layers of indirection. Testability is good of course, but it is a game of compromises, and for me, there are other priorities.

It makes sense at a high level though, like for public APIs. Ideally, I'd rather write both sides at the same time, as to have a real use case rather than just a test, and have both evolve together, but it is not always possible. In that case, writing the test first may be the next best thing.

>It makes sense at a high level though

This is the way I've always done TDD.

I don't think it makes sense to do it any other way. If a test case doesn't map on to a real scenario you're trying to implement the code for it doesn't make any sense to write it.

I find that people who write the test after tend to miss edge cases or (when they're trying to be thorough) write too many scenarios - covering the same code more than once.

Writing the test first and the code that makes it pass next helps to inextricably tie the test to the actual code change.

>but it is not always possible

I don't think I've written any production code in years where I gave up because it was intrinsically not possible.

I do TDD and write proofs as tests. TDD practitioners never said TDD is a substitute for thinking.

> Problem is, when you write tests first, especially tight, easy to run unit tests, you will be tempted to write code that pass the tests, not code that does the right thing. Like throwing stuff at your tests and see what sticks.

I never had that problem, but I knew how to code before I started TDD.

The flow here for me is if the code is doing the wrong thing I:

- Write a test that demonstrates that it is doing the wrong thing

- Watch it fail

- Change the code to do the right thing

- Ensure the test passes

And in return I get regression prevention and verified documentation (the hopefully well structured and descriptive test class) for almost free.

I don't think any amount of testing absolves the programmer from writing clear, intention-revealing code that is correct. TDD is just a tool that helps ensure the programmers understanding of the code evolves with code. There have been so many times where I write code and expect a test to fail/pass and it doesn't. This detects subtle minute drift in understanding.