For many people (myself included), Test Driven Development is far from instinctive. It even takes some inward reflection to learn how you, as a programmer, design and architect while coding. The very first question I had was “How do I drive development with tests?” In some ways this is a can of worms. Deeper and deeper you’ll tumble down the rabbit hole, because TDD is exciting. Let’s start with the step-by-step recipe for TDD:
- Write a test that fails.
- Write code to make the test pass.
- Refactor ruthlessly to simplify.
- Rinse, repeat.
That is a breakdown of the mechanical process. Knowing it is only the first step. With this in mind, I would like to share my opinions and observations of this design method.
Unit tests can read like low level specifications. A practical example is that someone jots down some pseudo code and asks you to write a class based on how they would use it. Fundamentally, TDD is taking that idea and going pro.
Writing tests as simple examples is a great starting point for high quality design. Focus on showing how simple it is to set up an object. I have noticed that writing tests first influences me to write shorter methods, as well as simpler classes. Small chunks are easier to understand, and TDD can definitely help you stay on that track.
Some other side effects of writing the tests up front is that you end up with a suite of tests at the end of the process, resulting code is more extensible , easier to maintain, and it is a way to gain instant gratification. It is somewhat thrilling (okay, maybe that’s just me…) to see a test compile and run.
All of these things are great, but let us remember that TDD is design. Tests are a great part of the process, but it’s something we are borrowing as the means to achieving design goals. By nature, test driven development lends itself to the SOLID principles.
TDD especially lends itself to the “Single Responsibility” principle. Singleton objects tend to be easier to test, and help create clear dependencies.
”If you start with simplicity, and expectations and demands for simplicity, you’re going to end up with higher quality design.” - Scott Hanselman
The overall process of TDD helps to detect code smell, or any symptom in the source code that indicates something may be wrong. As a rule of thumb, if you don’t enjoy setting up the test, something can probably be improved. This is referred to as “Test Friction.” There should be a threshold of discomfort when setting up complicated tests.
Another TDD warning flag is solubility—how hard is it to understand code?
“How much effort do you have to make before you can learn what an object does?” – Scott Bellware
Code smell is an excellent indicator that code needs to be refactored. Having the suite of unit tests that you (presumably) wrote before (or at least while) coding will be the safety net for refactoring. Refactoring is successful if tests pass.
References:
Hanselminutes, Episode #31- Scott Hanselman discusses TDD with Carl Franklin
Hanselminutes, Episode #146 – Scott Hanselman discusses “Test Driven Development is Design” with Scott Bellware