Robert C. Martin describes 3 very basic rules of TDD. These rules sound very simple. But as simple as they sound, as hard it is for a programmer to get into the Red-Green-Refactor TDD flow. I had many unsuccessful attempts, and, many times, TDD seemed stupid and time-consuming. But, as I kept trying, I had some revelations, some “aha ! moments”. Now I managed to get into the TDD flow, and I enjoy it very much. I would like to share some those revelations, that I think are very important and each programmer should go through them to understand the benefits of TDD.
Unit tests are first class citizens
In my first attempts at doing TDD and writing unit tests, I used to think that everything goes in unit tests as long as I get the coverage. One of the first AHA moment was when I realized that tests are first class citizens of the code base. They don’t just have a supporting role. That means that all principles like SOLID and DRY apply to the tests also. The refactor stage of TDD applies equally to tests as it does to production code. This makes writing tests as fun as writing production code, not a boring task that you have to do to get the story to close.
Before realizing this, the tests I used to write were very brittle and hard to maintain. Hard to maintain tests are almost as bad as hard to maintain production code, because testing should enable change and take the fear away from refactoring code. But, if the tests are brittle or hard to maintain, they do the exact opposite because you will be afraid to change code because tests will fail and it could be a nightmare to get them to pass again.
Unit tests are documentation
There are a lot of presentations and blog posts stating that unit tests are documentation. One of the biggest ‘aha’ moments I had in my quest to TDD, was realizing that unit tests are actually documentation. This depends a lot on the way that the unit tests are written. But good unit tests are the best documentation that you can create for your code. DocBlocks or inline comments can easily become outdated, because as software grows they often get overlooked and get out of sync with the code that they are describing. Because unit-tests need to be ran all the time, they have to change alongside the code and are always in sync with it.
Misko Hevery, the creator of angularJs, asked a very interesting question in this presentation about good unit tests. If someone would delete all your code, would you be able to reconstruct it with only the tests to guide you ? This question seemed very interesting to me. I was writing unit tests that were testing too much, had too many assertion, and would cover a lot of code or even multiple classes. For example,
testRegistration doesn’t say very much from an unit test perspective, but
testItShouldThrowAnExceptionIfUsernameExists makes much more sense and it states exactly what the code should do.
It is important that tests do as few assertions as possible, ideally one. Once you realize this, the tests start getting a new form. If you follow the rule that you should make just one assertion per test, it becomes a must to refactor and organize the tests to respect the DRY principle.
Every phase of TDD is equally important
All phases in TDD are equally important. The refactor and red phases often get overlooked and this could cause problems. Here are the reasons why:
Red allows you to validate your test. If you write the tests after, and it works, then there are two possible scenarios, your code works, or your code is broken and the test is broken (you could have a bias to writing a bad unit test just to validate your bad code). Seeing the test fail ensures that it’s actually testing the code you are going to write.
Green ensures you that the code you had just written makes the test pass.
Refactor enables you to think about the structure of the code to make it cleaner, more readable and more generic. All this while the tests are all green. It is very important that you never refactor when the tests are red because this could lead to hard to find bugs.
TDD is fun
This is just a personal opinion, but since I got into the TDD flow, coding is much more fun. It makes writing tests as much fun as writing production code. Each green phase gives you a small accomplishment that all code works. Even more fun, I think, is the fact that I can experiment with the code, move things around, improve it and all this while I am sure that I won’t break any existing functionality because the tests are green. This allows me to continuously improve the code and make it cleaner and more SOLID.
Everything just works
Usually, when I was working on a project, it was very easy to get 80% of it done. The problem was in the last 20%, the details and edge cases. By doing tdd I noticed that when I think I am done, I am really done. I developed some pretty big modules with TDD without running the code except in tests. When I thought I was done, I just registered the services and dependencies in the Symfony DI container, and just called the respective service in the controller. The only bugs I found were typos in the DI container, but those usually take minutes to fix and then everything just works.
Writing code faster
It seems to me that development is much faster because I get quicker feedback about what I am doing. Before, when developing, I used to write code and then go in the browser and run the code to see that everything what I had done until then worked. While doing TDD, I just press the “Run tests” key in my IDE and I know everything is ok if I am on the green. With unit tests running in under a second, the feedback loop is much shorter and the development time much faster. As a plus, I almost never have to touch the debugger.
This is just a list of the things that I realized and which helped me get a grasp on TDD. I will write a few followup posts detailing each idea more in-depth, with more details and code samples.