TDD - What is it?

Is TDD not working? Or is it?

Every now and than I hear (read) people mention TDD in the context of hard, not working, not useful and even something that makes no sense. Usually it boils down to something that people do that they call a TDD (that is not TDD) that they dislike.

TDD is a way to write programs efficiently. Efficiently means many things. Here are some characteristics of efficiency: minimal size of the codebase, list of requirements and use cases program adheres to, ability to validate code changes.

TDD is the art of making baby steps. And if you worked in the industry long enough you know that this skill, the ability to break larger things into smaller ones, is the one of the most used skills at work.

How to TDD?

Here are all the steps one need to make to become a TDD-er (TDD-expert, Master on TDD):

  1. Make a list of tests .

  2. Pick a test from the list.

  3. Implement a test. Observe it fails.

  4. Adjust the code to satisfy the test.

  5. Go to step #2, if there are any tests left to implement. Otherwise you are done. Check in.

That is it. Yep! The complete algorithm. The process of TDD is complete. If you managed to do it step-by-step in the same sequence you definitely "TDD-ing like a Pro".

Obviously there are certain caveats, misunderstandings and whatnot. Below are some advice on how to best approach each step plus recommended materials for learning more.

Step 1 - List of Tests.

If you ever used sticky notes to keep track of your to-do things that is exactly it. Or just use one sticky to write down all the things your code is supposed to do.

Here is what to not do:

  • Do not think too much. Time constrain yourself to 1 minute.

  • Do not try to make it perfect.

Step 2 - Pick a Test.

Always start with the simplest, dumbest test possible.

Here is how one should be approaching this. Ask yourself "What problem am I solving right now?". Depending on the test there can be different answers. In the very beginning the problem is always in creating a shape (module, class, function, interface, code contract) of the code one is writing. The first test should help you create structure, the outline, of the code. It should almost explicitly require structure and nothing else.

The rule of thumb for choosing a test should be this: only one problem to solve. If you pick a test which is asking for more that one problem to solve your mind is going to be constantly going between solving one of the problems. Every tried to do two things at the same time? Yes, it is impossible. So, do what is possible instead.

Step 3 - Implement a Test.

So how do we implement a test? What is it?

Test is something that you can run and it provides you with definitive answer. The answer should be if your code is working or definitely not. For example: compiler, bash script. It is not required to use a unit testing framework in the first place but this is something you will stick to sooner or later.

It makes sense to break unit test implementation into even smaller steps so that each one would be validated as soon as possible. Here is an example. I write a test code for a function that does not exist. I compile the test code - compiler provides feedback (yells at me) that certain method is nowhere to be found. I write default implementation. Compile - all good. Run test code - it fails - as expected. What's next?

Step 4 - Adjust the Code.

It should be straightforward. Unless the test assumes to much or contains too many problems to solve. In any case there can be possible outcomes: adjusting the code to make test pass or write smaller test instead.

There can be bad tests. Bad tests will make you churn on the code for hours, if not days. If you think that it is taking too much of your time - try to break the test into smaller units of work. Write smaller tests. Smaller tests lead to smaller problems to solve and smaller code to write.

In the end, you should be able to run the test. It should pass. And that is going to be your pass to the next step.

Step 5 - Are You Done?

If there are any tests left move on with implementing them one by one to by moving to step #2. Simple.

Obviously, while going through the steps it may be tempting to implement all the ideas that are popping in you head. But instead one should be able to just append them to your list of tests. In this way one should be able to focus on the problem at hand and deal with the rest of them later.

This is one more thing. If you are a responsible software developer you should be tempted to write code that not only works as expected, but you should make it have good structure, readability and be performant. Do not try to achieve these qualify for all these requirements. The approach I recommend is as follows. First, implement the code you are looking to implement. You will have your code and tests that validate all its use cases. Now you can change its structure, rework it so it feels and looks right. All or any performance improvements should be the very last thing you do while working on the code.

Additional Learning Materials

TDD is as simple as these five steps. Now, there is definitely much more to the art writing unit tests. It goes way beyond TDD.

If you want to learn more here is where to:

  • Read this article from Martin Fowler about TDD.

  • Watch Uncle Bob's TDD (part 1 and part 2) introduction. He has advanced TDD

  • Read "Test-Driven Development" by Kent Beck.

  • Read "xUnit Test Patterns: Refactoring Test Code" by Gerard Meszaros.

  • Read "Working Effectively With Legacy Code" by Michael Feathers.