The mindset behind Test Driven Development (TDD)
Author: E. M. Yeaseenur Rahman Tahin
Senior Software Engineer
TDD, or Test Driven Development in full, is not a new concept in modern software development practices. However, many software engineers still find it very weird and contrary to common sense. In this brief article, I will try to clarify the TDD concepts and prepare your mindset to start TDD from today.
What is TDD?
Contrary to popular belief, TDD is not a testing process. It is a design process. It’s a robust way of designing software components interactively, one unit at a time, by making sure that the individual component’s behavior is specified through unit tests beforehand.
There are 3 laws of TDD.
- Write a failing unit test for a feature.
- Write just enough code needed to pass that failing test. Don’t bother about code quality at this stage.
- Refactor your code if possible.
It is a “break first, build later” approach.
In TDD, we write only a few lines of code each time to pass a failing unit test. So, our development cycle is very very short, which makes debugging very easy too. How easy would it be to debug, if we could know that our code was just fine a few seconds back?
Developers love to learn about software by reading code. Even when we work with any 3rd party library, we tend to move directly to the code examples. Unit tests do the same for your production code. It makes other developers’ lives easier by being able to understand how your feature should work.
Writing tests after writing the code is hard. As the code can be written without testability in mind, it might not be testable, or properly decoupled/modularized. Hence, we would need to change our code again to make it testable.
Writing tests subsequently after writing code does not bode well, since we already know that our code works. Writing tests before the code can be fun, as the test at first fails, and then we write code to make it pass.
When we write tests after the code, it leaves holes in the test suite, as we are highly likely to miss some corner cases here and there. When we run a test suite full of holes, passing all the tests means nothing at all.
One of the primary reasons for doing TDD is that it makes refactoring a piece of cake. Most of the time we avoid fixing bad code in our project as it might break the system. With TDD, we can be brave about refactoring as we know that the consequences of our changes are just a “run all tests” away.
Overcoming the challenges in TDD
Like most of the things in life, TDD is easier said than done. There are many challenges in our path. However, the good thing is, they can be overcome.
TDD seems counter-intuitive. How do I know what to test before I write the code?
If you think this, you probably like to dive straight into the code when you see a problem. This, unfortunately, is not the right approach. You should start solving a problem by some kind of design on paper or in your head. Once you have the design ready, you know what tests to write. Nobody is saying it is easy. It takes time. Trust me though, it gets easier with practice.
It’s a waste of time.
It is true that TDD makes developing the initial version of the code a lengthy process, but it is not a waste. Moreover, if you consider the time for debugging, refactoring, and later understanding the code, then the amount of time you save in TDD is incredible. Remember, there is nothing good about driving fast in the wrong direction
I have no idea!
It’s perfectly normal. You might be new to this or you need some hands-on guidance. Find someone around you who has the knowledge and experience with TDD. Let them help you.
You have no idea what I am going through.
Said every developer ever. Regardless of what project or technology you are working with, there is a way to do TDD in most cases. TDD might not be applicable if you are writing simple code such as getters/setters, or one-liners or otherwise without any business logic. The idea is to test everything that could possibly break.
Examples of TDD
Let’s say we have to write a function that will take ‘n’ as a parameter and will return the ‘nth’ Fibonacci number in the Fibonacci series. Can we do that using TDD approach? Let’s try.
First, let’s write our first test.
Now if we run the above test we will get the following output.
Obviously, we will get an error as we didn’t define any getNthFibonacci method yet. Let’s write the method.
Now, if we run the test, we will get –
Our getNthFibonacci method is not returning the right value. In fact, it’s not returning anything. Let’s fix that and run the test.
Hoorah!!! Now the test has passed. Let’s write our second test and run it.
Opps, the test has failed again. Let’s make changes in our code to pass this test and run the test again.
Great!!! Our 2nd test has passed.
Now, let’s write our 3rd test and run it.
Let’s write code to pass this test.
Let’s write another test to solidify our Fibonacci generation logic and run it.
Looks like our logic is fine. This is a good place to do a refactor. Let’s refactor this code to improve its readability and run all the tests.
Looks great. However, we want our getNthFibonacci method to throw an error if the ‘n’ is 0 or a negative number. Let’s write few tests for that and run them.
We need to write some code to handle these situations.
Marvelous!!! Looks like we have a solid getNthFibonacci method with a concrete set of tests. We just finished development using TDD approach.
That’s about it. My goal was to prepare you mentally for this unorthodox yet extremely useful approach. I hope some of you are convinced about the usefulness of TDD by now.
So, start using TDD before it’s too late, and don’t forget to have fun. Peace ✌.