I have always been keen on using automated unit tests since I first heard about them almost a decade ago. I have known about test driven development (TDD) for almost as long but the practice of writing tests first before writing production code never really clicked for me when I first tried it years ago. Since then I have evolved my approach of writing tests, but still almost always after I write the production code.
Recently I was prompted by multiple sources to give TDD a try, the most prominent and vocal of which was Robert C Martin, who stated that he believed TDD to be one of the most significant new software development practices he had adopted in his 30 years of experience. I found particularly compelling his comparison of TDD for software development to the medical practice of sterile operating rooms.
Based on these strong recommendations, I decided that I needed to give test driven development another try.
I resolved to not just haphazardly try TDD like I did before, but to adopt it as a development practice for a period of time as a continuous improvement experiment. I deliberately went with a more disciplined approach. Based on my knowledge of personal development and continuous improvement, I knew that the change would be difficult, especially at first. So I prepared for the change via the following steps:
- I reflected on the difficulties I would face in adopting TDD. I expected to struggle with two issues. The first was the drop in productivity due to getting familiar with doing TDD – I would be spending a greater percentage of my time thinking about my process of coding as it related to TDD, rather than the code I was writing. The second was the natural tendency to revert back to my established pattern of behavior. This reflection ensured I would have more realistic expectations when I started using TDD.
- I refreshed my knowledge of the details of doing TDD that go beyond the core idea of writing tests before code. My favorite reference was Robert C. Martin’s three rules of test driven development which are:
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
- I wrote a note to do TDD and stuck it to the front of my keyboard were it would always be visible to me. It served as both an affirmation and a reminder.
Having finished my preparation, it was time to actually start doing test-driven development.
My initial start with TDD was easy: I started my next coding task by writing a test rather than production code. If only it stayed so simple :)
At first the shift in process was difficult as I had to consciously remember to write the test first, and then write only a portion of the production code necessary to get it to pass. My productivity felt a lot lower (I have no idea whether it was significantly worse or only a little). But I had expected this and used discipline to force myself to continue with TDD.
One of the hurdles I faced was how strictly to follow the three rules of TDD - particularly rule number three. I had always been uncomfortable with the idea of writing temporary or intermediate production code that would get the current test to pass, but that I knew was not the final form and that I would need to change. An example is implementing an algorithm to return a fixed value (say zero or null) rather than implement the actual logic. I decided to take a pragmatic approach and allow myself to deviate from the three rules on occasion, when following the rules seemed too onerous or difficult. I did not want blind adherence to the rules to cause me to completely give up on TDD. I expected that over time as my familiarity with TDD grew, I would be able to become stricter in adhering to the rules.
As expected I suffered setbacks along the way. I started a development task by writing most of a method of production code before realizing that I had no failing test. In another case I had a failing test but then churned out the entire production method, including all the special cases, well after that test would pass. I took these setbacks in stride – I considered them a normal outcome of adopting a new behavior, rather than personal failures, and simply returned to doing TDD once I became aware of my departure.
After about a week, doing TDD became less of a struggle. After about three weeks (the typical minimum duration to establish a new habit) TDD began to feel more natural. By this point I had clarified for myself the benefits and limitations of TDD and had integrated it into my development process. I have a lot more to say about this which I will save for a follow-up post. As a quick summary, I find TDD to be a valuable practice that I intend to continue to use.
If you have not tried TDD, I strongly recommend you experiment with adopting it as a development practice. Looking beyond just TDD, one of the points of this article is to encourage you to always be thinking about your capabilities as a software developer and continuously seek to improve.
If you find this article helpful, please make a donation.