In one of my projects the "additional layer of code to automate this checking" looks like this:
def ok(a, b): assert a == b, (a, b)
from .util import ok
ok(Parse(b'.25').do(0, real), (3, ('real', 0.25)))
ok(Parse(b'1.5').do(0, real), (3, ('real', 1.5)))
ok(Parse(b'-1.5').do(0, real), (4, ('real', -1.5)))
ok(Parse(b'+1.5').do(0, real), (4, ('real', +1.5)))
If this results in an exception at import time, I sometimes have to comment out one or more of these tests while I fix parts of the bug.
I mean, I do do some manual testing. Sometimes it's easier to bang on a function and decide whether the output looks right than to figure out what the results should be from first principles. But it doesn't take very many repetitions of a manual test of something like the above before it's the manual testing inflating the time needed, not adding another ok() line.
This is especially true for debugging. Adding automated tests is often a faster way to debug than to add print statements or to step through things in a debugger. Sometimes I take the tests out afterwards, but more commonly I leave them in. And with things like Hypothesis and QuickCheck, tests can have a much higher strength-to-weight ratio than simple tests like the above.
It's also a breath of fresh air when I'm trying to understand what some code is supposed to do, or how to call it, and I come across a one-line test like the above.
Speaking of how to call things, sometimes I do test-driven development so that my internal APIs don't suck. And sometimes I don't and wish I had. There are a couple of lines of code in the same project that say this:
array = Thunk(lambda: drop_ws(b'[') + Any(pdf_obj) + drop_ws(b']'))
array.xform = lambda d: ('array', d[0][1])
And, you know, that's just shitty, shitty code, because that was a shitty way to design that interface. And maybe if I'd written that code test-first, instead of just charging ahead madly implementing the first thing that came to mind, I would have realized that when I was writing the test, before writing the implementation. Now, if I want the interface to suck less, I have 700 lines of crap to refactor to the new interface, which probably means either throwing it away and starting from scratch, or incrementally refactoring it while maintaining both interfaces for a day or two. And that's inflating the time I need to get the thing done.
It would have been less work to fix it at design time, the way my friend Aaron taught me: first, write some application code as if the ideal library existed to write it with; then, write the library code that makes it run. And that's just so much easier when that application code is just a unit test.
However, as with most things, I disagree with Robert Martin's point of view on this. I can easily conceive of writing software without TDD, or even without tests. In fact, I do it frequently. Tests are often valuable, and TDD is sometimes valuable, even in weekend personal projects. But they aren't always worth the cost.