It's simple:
By learning to think about the "What" __before__ you think about the "How"
In other words, think about exactly what you want to do (interface), rather than you how you are going to do it (implementation)
TDD, as a design tool, based on my experience, really helps you look at things from the user perspective than the coder perspective. In addition, I think TDD helps your mind really thinks about what exactly you are trying to do, what's the expected outcome, etc.
So next time when you are trying out "TDD," ask yourself what it is you are trying to do, and just start writing the code that expresses your intention.
Example:
Say, someone wants you to write them an integer adder.
The person that does not have a TDD mindest will simply just do:
int add(int a, int b)
{
return a + b;
}
Is the above code correct? Sure it is. But this approach, based on my experience, fails when you have a complicated component to write. (That's why you asked this question in the first place; I know, I had been there before (perhaps still? lol))
The great thing about TDD is that it forces you to give attention first and foremost to the interface (the what) of the system, while not asking you immediately for the implementation (the how).
In other words, if someone asked me to write an adder, I would have something like:
void assertOnePlusTwoEqualThree()
{
assert( add(1,2) == 3 );
}
Notice, how, even before even thinking about how the add() is supposed to work, I already ironed out a few things. That is, I already figured out:
- the interface of my adder both inputs and outputs
- the first trivial test case (unit test for free!!)
then you implement the add().
See, it doesn't matter if the logic discussed here is so simple to implement. To have a TDD mindset, is to apply it all the time with no exception. You have to do it so many times that you don't even think about it anymore. It's just part of how you design. I can say this because I saw it happened to me professionally (it took about a year of perseverance).
Just like how if you always do a good job on programming, regardless of the complexity at hand, you approach your job the same way.
Lastly, I think TDD is comparable to "code sketching." That is, you start trying out to see if the interface works well for each scenario (test case). So it's okay if you end up changing the interface, and the names, etc. See, what I've learned is that a lot of times, design is simply about understanding the problem thoroughly. TDD is one tool that allows you to do that.
Human mind, IMO, works easier with concrete examples (test cases/scenarios) rather than abstract thinking (how to implement something). By having test cases, it allows your mind to gradually learn about the problem you are trying to solve at hand.
It's okay to not know (going back to the "traditional" way...relax, let your mind adjust). It's okay if it's not perfect (writing some implementation code is perfectly okay...your brain is just trying to figure things out). Just keep trying, keep reading, keep coding, but never give up! =)