My strategy has generally been to throw some tests that cover most things in a general way, and when I find bugs, add lots of detailed tests that make sure those bugs will never come back again. That makes it feel less pedantic.
That's actually the opposite of the truth. The second D is for Design (or Development). The idea is that you organically develop the spec and the code in tandem.
If you weren't using TDD, you might go off and create a BowlingPin class, a BowlingBall class (if it's not extant), and hack together a method in some arbitrary order and method. That's cool, but the TDD approach would have you write a mini "test." In pseudoRuby:
This, of course, will fail because there is no BowlingPin class. So you write the class skeleton and re-run the tests.
Fails again. Why? There is no got_hit method on BowlingPin? Shit. Now you've got to go and write the simplest got_hit method that'll work. So you write something like:
Rerun the tests. Dammit! There's no "fell?" method -- gotta implement it! And so on and so on.
There are a few things to note here. First, it's pretty critical to use a decent test harness. Running and writing tests has to be crazy fast otherwise this approach won't work well.
Next, in running through the TDD cycle you're actually (1) specifying the interfaces and behavior of your code and (2) implementing as narrowly as you can. The actual "tests" you get out of it are almost secondary! If implemented properly, it's really a design methodology. That's why the new trend is to change the language from Test Driven to Behavior Driven. The use of the word "test" seems to (understandably) evoke the same response you had of needing an iron-clad spec to work from initially. It's just not the case.
I think a lot of people here are confused about what exactly a unit test is. When you write a unit test, you don't say: "I want a bowling application, with such and such a scoring method, and such and such a pin layout".
It's more like - "I am writing a class (or portion of a class) here that's going to perform a certain function in my 'blue sky', exploratory phase project". If you don't know what your code is going to do, how can you really write it. That is to say, exploratory coding isn't just typing random characters. You have some ideas.
So, instead of just writing the first function/method/class/controller action/model, you write a test first, and watch it fail. Then, you implement your method. That way, you know that your method works.
You really don't need to have an overall big picture of your application to write tests. That view is more of a misunderstanding of testing than anything else.
That doesn't strike me as an optimal use of time.
On the other hand, once I'm pretty sure of what things should look like, yeah, test cases are invaluable in demonstrating that everything works like it should, that corner cases don't break things, and that any new bugs stay fixed. Especially if other people have to work with the code, and don't know it well, they can wade into it with confidence that they can be confident they won't break anything (or at least are less likely to).