I remember when I first was introduced to the concept of Test Driven Development (TDD). If you're not familiar with the concept, the idea behind Test Driven Development is that you write a failing test first, before you write any code. Then you make that test pass, write another failing test, make that one pass, and so on.
I am sure I am not the first person to question whether there's really enough value to be had from this methodology to devote half or more of the code that you write to code that will never make its way into something a user will ever see. And the answer to this that I saw, over and over, was "just try it, and then you'll see." And that seems to be the answer to a lot of "why should I use up and coming technology x" type questions, so I typically have a really long lists of things I need to try so I can see why I might benefit from them.
You may be the same, so today I am going to list the benefits I have seen from trying TDD.
Why Test?
This is one of the answers that is easiest to find about TDD. When you have unit tests for your code, you can change the way your code works internally. If the unit tests all still pass, you can have a fairly high degree of confidence that your changes didn't break anything.
Why Test First?
The answer to this question is less intuitive, but becomes obvious once you're actually writing unit tests. If your test fails first, you know for sure that it's actually running. So, you didn't forget to include it in the test suite and you set it up properly. Next, you know that the code you wrote to make it pass is what made it pass. So you're not testing an incorrect assumption and your test code actually tests what you think it is testing.
Benefits of TDD
Now that we've gotten these basic questions out of the way, let's look at some more specific benefits that you can start seeing right away using unit tests.
Less Cognitive Load
Let's imagine a scenario where you're using a Presentation Model that contains information that you're retrieving from a web service and storing on a model. You need for a Factory to create a View, instantiate the Presentation Model, give the View a reference to the Presentation Model, and then pass the View to your Controller to add the View to the stage. The steps you need to go through before you can even compile*, much less run the code are:
- Write the Presentation Model
- Write the View
- Write the Factory, or add logic to an exiting Factory to handle instantiating the new View and PM
- Add logic to your Controller to handle interaction with the Factory and adding of the View
- You may need to write a Service to get the data or expand your existing Services
- You may need to add logic to your Model to hold the needed data
Once you reach that point, you probably have a lot of compile errors to clear. Then and only then are you in a position to see if any of it works even a little (it probably doesn't), and you get to dig through the entire system to see what went wrong and fix it, piece by piece.
The steps to get your code compiling/running using Unit Testing:
- Write a unit test
- Write a method on the Presentation Model
If something went wrong (it should, you planned for it to fail), you know exactly what method contains the fault. You fix that problem, write the next test, and keep going. You don't have to keep the whole structure in your head the entire time you're working–just the desired effect of the method that is currently under test.
The only time you need to reference your mental image of the entire house of cards is when you move on to the next method or Class. Because you're able to focus completely on a smaller aspect of the system, you're more likely to anticipate problems, write tests for them, and get the Class right.
Even Five Minutes becomes a Meaningful Unit of Work
I don't know any developer that mainly has large, uninterrupted swathes of time in which to code. If you're that guy, I envy you. The rest of us often have a lot of wasted time because the five or ten minutes before the next meeting, quitting time, lunch, or whatever, just isn't enough time for us to make inroads into complex designs with interconnected Classes.
With TDD, I can write a test in about two minutes. Implementation of whatever needs to happen to make the test pass can take as little as another two. If all goes well, I've accomplished something meaningful in under five minutes. If it doesn't go well, the failing test and half-baked implementation are a great "bookmark" so that I can jump right back in to where I was before the interruption.
Plan for future functionality
If I am using a Class for one thing today and I can see where it would be useful if it could also handle additional scenarios in the future, I can go ahead and write tests for those scenarios and implement the functionality to handle those. That way if I'm not the first person on the team to encounter that new scenario, I can confidently tell that team member "yes, you can use this Class for that." This feels so much better than saying "I put logic in there that should handle that case, but it's never been tested."
Another advantage of this is that when a team member asks for example code of how to use the Class with his new use case, you can just point to the unit test.
Better Accountability
This is, in fact, the reason I started unit testing. If the software I am working on isn't right, it has the potential to literally sink the Titanic (ok, maybe not the actual Titanic, but some other boat). The Project Manager insisted that every person on the team be responsible for making certain her part was exactly right. TDD was the only approach I knew that held the promise to allow me to do this.
If you have code that is proven to always have good output when given good input, it's easier to know that the problem is somewhere in the input and track back to there. And you have the ability to show others on the team what circumstances could lead to bad output from your code. This allows for the debugging effort to focus on where the problem actually is, rather than every person whose code touches the problem having to dig through and determine if his code could have caused it.
These are just a few of the benefits I have found in the short time I've been using TDD regularly. If you're using TDD, please feel free to add the benefits you've seen to the comments.