Red, Green, Refactor — is the way of Test Driven Development. Even though TDD is not a new thing anymore and it has been widely adopted in our industry, in reality, there are many software projects who do not have any test coverage at all and there are many developers who simply refuse to adopt the practice. Extensive test coverages are still very rare.
The excuses are plentiful. Not enough time, test coverage is overrated, tests are hard to write and management does not allow tests to be written in the first place. Also it might seem like some developers are just too lazy to add tests to their code. I don’t think that laziness is the problem here.
Writing tests after the implementation is especially hard to do and here is nothing more counter intuitive than to writing many lines of mocking code to test a single function.
Over the years, I have joined a lot of teams that were struggling with adopting TDD. I have also helped others to get into the habit of writing tests. Introducing tests to an existing code-base is often an uphill battle, especially when the existing architecture makes it necessary to write a lot of stubs and mocks. However, in the long run this has always led to more stability and faster development cycles.
I remember one particular project where I have spent a whole year of adding tests for the existing code. After many months of work, we had reached a test coverage of one hundred percent. Our increased confidence in the software that we shipped, gave a huge boost to the velocity and moral of the whole team.
Since then, having high code coverage has been a priority for all of my projects.
This dedication has certainly paid off.
There have been many times when the tests have helped me to identify issues or bugs that I would surely have missed. A bug is just something that is not covered by a test. Add the test, fix the bug — done.
But most of all, having an extensive test coverage is invaluable when refactoring. Especially when I need to change thousands of lines of code. Everyone would agree that it is almost impossible to verify that everything still works through manual testing.
When working on a large system it can be cumbersome to adjust or implement a feature that involves a lot of manual work to test it.
Here’s an example:
Let’s say we want to improve the registration process of a certain app. The new code is located somewhere between the time when the user hits ‘confirm’ on his registration form and action that triggers the confirmation e-mail being sent to the user.
The manual steps would include:
- Set a breakpoint in the code your are adding
- Go to the registration page
- Fill out the registration form, Name, First name, email etc.
- Click confirm
- Check the debugger and see if the values are correct
- If they are not — change the code and repeat the process
There is a much better way. With the help of unit or feature tests, I can keep my eyes on the code, while I figure out the implementation details. The tests help me to verify that everything works as expected.
More importantly, I have reduced the time of my “feedback loop” from a couple of minutes, down to a few seconds. At the same time, I stay focused on the task at hand, without being distracted by what might be going on in other browser tabs.
This is just a simple example. Most scenarios will involve a lot more manual steps.
As I am writing this, I am in the process of making a huge refactoring in one of my applications. This involves the dropping of database tables, changes in the business logic and the deletion of hundreds of lines of code. It would be impossible to validate that everything still works with manual testing.
A major refactoring like this one will cause many tests to fail. This is a good thing. I can go through these tests one by one and check if the failure is caused by a change in the API, or if have missed something else.
This brings me to one of the most important aspects of Test Driven Development: Confidence.
Even though there might still be errors in the code, I can be sure that every-thing — that I have written tests for — still works.
At the end of the day, I can deploy my code and do what I want on a free evening. There is no need to worry. The tests have me covered.
Test Driven Development is an area full of opinions and misunderstanding. It is such a controversy that it has almost turned into a Holy War.
All I can say is: It works for me.
I have been practicing TDD for more than a decade now, while working with many languages and testing frameworks.
It has never been easier to get in the habit of writing tests. For most languages there are excellent testing frameworks available and there is plenty of documentation out there. Also, with the advent of AI, coming up with unit tests for a certain piece of code is easier than ever. Github’s Copilot and ChatGPT produce very good test code that can help you get started.
Once you get into the habit of adding tests to your code base, you will
come to appreciate the stability and joy it adds to your development workflow.