It depends of how much control I have over the topic. If:
- I know the topic well.
- I understand the domain well.
- I can picture the technology, expectations and API well.
- I have the mastery of my time and deadline.
Then yes, I do.
E.G: I'm currently making a proposal for a new API in an open source python lib called iohttp.
I have time. I have leeway. I have a good overview of the entire problem. So I can afford thinking about the API first:
https://github.com/aio-libs/aiohttp/issues/4346
Then I will write unit tests. Then I will write the code.
But that's a rare case.
Many times coding involves fiddling, trying things out, writing drafts and snippets until something seems to start to solve what you want.
Other times, you want a quick and dirty script or you have a big system, but you are ok with it to fail from time to time. The cost of testing is then not worth it. You'll be surprised of how well a company can be, and how satisfied customers says they are despite the website showing a 500 once in a while.
And you course, you have limited resources and deadlines. Unit tests are an upfront payment, and you may not be able to afford it. Like it's often the case, this means the total cost of the project will likely be higher, but the initial cost will be in your range of price. And you may very well need that.
One additional things few people talk about is how organizations can make that hard for you. You may be working in orgs where the tooling you need (CI, version system, specific testing lib...) may be refused to you. You may even be working in companies where you cannot get clear information about the domain, but get a vague specs, and the only way to design the software is to ship, see it break, and wait for customers to report to you because otherwise the marketing people don't let you talk to them. I'd say change job, but it's not the point.
At last, you have the problem of the less experienced devs. TDD is hard. It requires years of practice in the field to be done properly because you build a system in a completely abstract way. Dependency injection, inversion of control, mocking and all that stuff you need to make a properly testable system is not intuitive: you learn it on the way. Even harder is the fact you have to use it in your head since your are not coding it, but what wraps it, first. And even more terrible is the fact that baldly implemented, over used and over engineered, design patterns make problem worst, not better.