I remember one of my CS professors at Calvin starting class one day by talking about TDD (Test Driven Development). “Write your tests first, then write the code to make the tests pass,” he said.
At that point in my programming career, TDD and unit testing in general just seemed like extra boilerplate code required to make my professor happy. If my assignment didn’t have tests (or good enough tests), I got a bad grade. However, I rarely, if ever, actually followed TDD principles when I was on my own—it just didn’t make sense to me. Write the code, test it manually, and then solidify it with some automated tests—that sounded like a better use of my time.
Jump forward to my first job out of college. With no one watching to make sure I was following TDD and with coworkers rarely writing unit tests and relying solely on Tester Driven Development (see what I did there?), it was easy to lose the best practices that I had learned in college.
However, as the anarchy continued, those TDD principles actually started to make practical sense. I often found myself thinking, “If we had written tests for this module right away, we could actually refactor this code without any problems,” or, “If we had written tests for this module right away, we wouldn’t be fixing this bug in production.” I now understood why my professor was so excited to teach us about TDD—a concept that at the time seemed so pointless and theoretical.
Even after coming to Atomic Object, it took me a few months to get totally comfortable with TDD and with religious unit testing in general. It still felt a little too theoretical for me—like if I had more time, I would do it, but if I were in a rush, I’d skip the tests and just jump right into being productive. The longer I practice TDD, the more I realize how foolish those instincts actually were.
Why I Love TDD
While there are entire books dedicated to the topics of TDD and software unit testing, there are a few especially practical benefits I discovered in my personal experience:
1. Higher-Quality Code
One of the obvious benefits of tested code is, well, that it has tests to validate its correctness. No one will argue that untested code is generally of higher quality than well-tested code. What’s less obvious is that when you are forced to think about how to test your code, you are also forced to think about how to write your code to be testable. This leads to much more modular, readable, and better designed software. You must put significant conscious thought into how you want your code to behave and how all the pieces should fit together, and this leads to a much better designed and higher quality piece of software.
2. More Confidence
As developers, especially Agile developers, we need to embrace change. Change is the backbone of Agile—the reason why it exists as a methodology. TDD gives developers the confidence required to refactor when a refactor is needed. If you are relying on manual testing for your confidence, then a major refactor is the riskiest and most costly thing you could do. After all, you and the testers just spent months solidifying that code, so how can you throw it all away and start over? With a robust suite of automated tests, a refactor is easy because:
- It was written to be more modular in the first place.
- It’s easy to incrementally validate the refactor via automated tests. You get almost instant feedback by running your existing tests.
3. Streamlined Debugging
If someone finds a bug in your code, the first thing you want to do as a developer is reproduce the issue. Most often, the easiest method to reproduce a bug is to simply write a test that exploits it. Rather than spending endless hours trying to reproduce it manually, you should spend a few minutes writing a test or a series of tests to reproduce the bug. Then, once you have a reliable way to reproduce the bug with your tests, you have both a starting point for fixing the bug and an objective way to verify that it is actually fixed. With an existing, well-maintained test suite, this debugging method becomes a natural solution; however, if your tests are always broken or just non-existent, then debugging in this manner is not feasible.
Test Driven Development can be a useful tool for virtually every software developer. It can be difficult to fully wrap your head around it initially, and it can be a big waste of time if you don’t fully commit to it. I think this is why a lot of companies are afraid to take the leap of faith and start using TDD rigorously—it is scary to put so much time and money into writing code that isn’t user-facing or “production software.” Also, if you start practicing TDD but don’t keep up with it, all the benefits disappear as soon as you stop trusting your tests; you will never rely on them again.
What has your experience with TDD been like? What benefits have you seen? Are there any areas where TDD has turned out to be the wrong process?