14 years ago, when I started my professional career as a programmer, my team leader conducted a week long session where everyone in the team had to write tests for several components in the application we built. No one in the team had ever written tests before, and it was very challenging. At the end of the week I had a short talk with my team lead where I conveyed my skepticism about automated tests. I was unable to grasp the concept of code checking other code. Since all code written has bugs, what prevents the tests from having bugs themselves or even work at all? How do I validate the tests? Should I also write tests for the tests?
This logic was the main barrier that prevented me from fully embracing automatic testing. For the next couple of months, I struggled with that thought, until I came across TDD. Since then, I never looked back.
Its pretty simple actually: good test is not only green when the code is correct, but more importantly, it is red when it has a flaw.
When writing tests for existing code, there's no actual way to make sure the test will fail. Think about it - all the logic is there, so what you only see are green tests. I can't count how many times I did a mistake in the assertion code, or even forgot it, and still the test I wrote was green. Most testing frameworks rely on opt-in mechanisms for failing the tests, so these will always be green without the writers or maintainers noticing (until a production bug is found and needs to be solved).
In TDD, the practice is - write a test, see it fail, write the code, see it pass. This simple process makes sure the tests are valid - they will fail when they need to fail. But, even better - the code and logic are now covering the test!
Yes, you read it right. The code is covering the tests.
I'll let it sink in for a minute.
Writing the code - which you are sure is correct - and seeing the test is still red, raises a huge red flag. Either the code is incorrect, or the test is. It will trigger a debug session that will validate which one is incorrect, and after that you can rest assure both are valid and good (when this happens I usually comment the code to make sure the test is red, and then un-comment it to make sure its green... Better safe then sorry).
For me, TDD answers the "chicken & egg" paradox for tests. The tests and code becomes a symbiote. Each benefits from the existing of the other. Each validate the other. And that's what makes it all work perfectly.
Of course there are other benefits for practicing TDD, but this simple concept is the main offering for me.
Once you understand this principle, you too, will never look back.