Test-driven development (TDD) is the act of writing tests before writing any code. Also known as red/green testing because you write the test, it fails, and then you write the code that makes it pass.
This process has a lot of different benefits such as simpler designs, more test coverage, and improved code quality. It provides a structure for developers to operate within that can often yield useful coding standards. If a team adopts TDD then all developers write tests for the code they write.
But if you are completely new to TDD, then getting started with it can be fuzzy. It turns out that it is quite simple to get started.
Dip Your Toes Into TDD
In the ideal world, all features have their expected behavior ahead of time. But in reality, product managers change their minds, use cases shift, and expected behaviors shift with them. This adds a twist to TDD that can be frustrating at first.
For TDD newbies, trying to write tests before a feature is even developed can be a mental blocker. You feel stuck or slowed down by TDD because the expected behaviors are not nailed down yet. Often times you have to iterate on your tests as requirements change. This is not a bad thing, but it can be a challenging place to start.
In fact, it is this twist that causes stakeholders to say "TDD is taking to long". The reality is that TDD is all about setting yourself up for faster development in the future. Every test written is a notch in the belt of better code quality and faster development.
If you are working with a legacy code base, a TDD experiment is just one bug away. Every piece of software has them, and they tend to have expected versus actual behaviors. These are great areas to start applying TDD concepts.
Follow these 7 steps to get familiar with test-driven development.
- The bug must be reproducible and have expected behavior.
- Now find where in the code the bug is at.
- Create a unit test that has the expected behavior.
- Run your new test and see that it fails.
- Update the code to produce the expected behavior.
- Run your new test again and see that it passes.
- Perform any refactoring on the code your test covers.
Just like that, you have applied the TDD concepts. Simple right? Almost too simple. There are some important details that I glossed over in regards to a couple of these steps.
The bug must be reproducible and have expected behavior
If there is an open question of whether is a bug than it isn't a great bug to start your TDD adventure on. Why? Because these type of bugs have unclear expected behaviors, so what is your test suppose to test?
Create a unit test that has the expected behavior
In legacy systems that have unit tests already, this might be trivial. If you are in a system that does not have any unit tests already than you are breaking new ground. With new ground comes refactoring code. Why? Because these codebases often need to be made testable. Adding dependency injection, making classes and functions single responsibility, and maybe using interfaces.
Perform any refactoring on the code your test covers
This is campsite policy, leave the code better than how you found it. If you think it is spotless, double check with someone on your team. The code is a living organism that evolves with every commit. It must be maintained, cleaned, and optimized where it can be. With good unit testing in place, you can do this maintenance and know if you broke something you shouldn't have.
What about features?
Once you are familiar with TDD concepts you should apply them to new features as well. The process is the same for the most part. Some folks write a few lines of the feature and then write the tests. Others write all the tests and then write the feature.
There is a spectrum of TDD folks that have very strong opinions on which of these correct. I am of the opinion that they are both great because there are tests either way.
What about the changing requirements? It's not a big deal. Why? Because if the requirement changes then you update your test, see it fail, and then update the code. By putting in the work to write tests before/in parallel with your feature than iterating on your feature is a breeze.
Test-driven development (TDD) is a very powerful tool in creating software. It enables a team to develop maintainable and high-quality code. But, it is not the only tool. Pair programming, bite-size stories, and fast iterative development are critical tools to have in place as well.