Saturday, June 4, 2011

Unit Testing, or Lack There of

It's probably been about 7 or 8 years since I was first introduced to JUnit and the concepts of TDD. Since those days, it's been clear that this is clearly a vital part of performing professional software development. Yet, for some reason, until recently my coverage has not been at the levels it should be. In fact, I've noticed it's not just me. Over the years, even as I often hear those around me advocating for more unit testing, rarely have I seen a project with even close to 50% coverage.

Why is it that something we think is so fundamental to our jobs so often gets left out of our projects? I think there are a few reasons:

Unit testing is hard: The fact is that writing a unit test is often harder than writing the code itself. As soon as I know the problem that I need to solve I quickly have a picture of the algorithm in my head, and often it will be simple to just throw it together in code. In order to test it, I need to think through all the nuances and write a test for every situation under which that code might reasonably be exexecuted. For example, let's say you were to create an equals method on an object that matched 2 fields. You could probably throw that together in a couple of minutes, but to unit test it, you've got several scenarios off the bat just accounting for null values.

Time constraints: The most common excuse I've heard for not writing unit tests is that developers are under a tight deadline to get something out. Because tests are not part of the deployable production system, they are treated as less critical and are often left out.

Tests are not written first: The mantra of TDD is that "before a single line of production code is written, a test must fail". This is something I personally haven't take seriously over the course of my career, nor have most of the people I've worked with. I've often stated in the past that "I don't really care if the tests are written before or after, just as long as they're there". I now know this to be a big mistake for 2 reasons:
1) I'm more likely to skip writing the tests because of time constraints
2) The tests are being tailored to the code, instead of code written to make tests pass

When it comes time to point the finger on these things, we need to aim them squarely at ourselves. It's easy to make excuses like those I've listed above, but we have the responsibility as professionals to be better.

While writing tests is hard, there are tools and methods that make it easier. One of the really tough parts is mocking out dependencies, which often takes time to understand even from a conceptual perspective. I've tried several Java mocking frameworks, but have recently started using Mockito, which I find very easy to use and able to handle all of my mocking needs. There are also lots of good ideas for making Unit Testing easier in xUnit Test Patterns.

Yes, it does take more time to write code with Unit Tests than it does without, and yes, we are under pressure to get our products out as soon as possible. Here's a typical conversation I've heard:
Developer: I expect that this task will take me about 2 days
Project Manager: Really? That long? But it's just slapping one more screen into this existing product. You sure you can't do it quicker?
Developer: I suppose you're right, I could probably throw it together based on another page and have it done in 1 day.

Does this conversation sound familar, at all? Constantly, I see managers and stake holders unhappy with estimates try to pressure developers into shorter estimates. Just as often, I see developers who sugar-coat or lower their estimates in order to please others. As professionals, it is our responsibility to be clear about our estimates and account for unit testing and other quality factors in those estimates. We need to educate our stake holders as to the benefits of unit-testing and the consequences of not. If you're not sure what those are, check out this page on Wikipedia.

I'll let you in on a little secret that I've discovered since I've earnestly started practicing TDD. Although it takes a while to write my unit tests, writing the production code is faster. In fact, it's alot faster. Once my test is written, writing the code to make the test pass is usually dead simple. One big reason for this, is that I rarely need to debug my code anymore. I write the code and run the test, which will either pass or fail. If it fails, I can quickly determine the cause and adjust my code. Now, if I am debugging, I find it's typically because I've made a mistake in my test.

In addition, I feel very comfortable making significant changes with very low risk. I can feel free to quickly make any refactorings I feel are needed without the risk of breaking anything.

Recently, I needed to refactor a very complex method that was designed to merge one set of data into a list of another set of data. It was a 150 line method with no pre-exsiting tests and had several levels of nesting. I decided that trying to modify the code would be unwise without having unit tests to verify the code, so I set out to do so, which took maybe an hour or so. Once my tests were written I was clearer on the intent of the method and how to better accomplish it. I was able to then re-write the method in about 5 minutes. It was now less than 10 lines long as it was reduced to a single if statement inside a for loop.

Was this success story because I am such a great developer? No, it was simply that I had good tests that made sure a method did what it was supposed to do, so I was able to find the simplest way to do that without making the tests break. The more and more I practice TDD, the more of these types of successes I am having.

I've said alot, but my point is this. We all know the value of unit testing, but we somehow end-up falling short in our delivery. Until we stop with the excuses and make TDD our primary development practice, we will continue to fall short. It needs to be a habitual part of our daily work, and we need to be vocal about it in order to get buy-in from those around us. Having un-tested code simply isn't good enough. We need to be better.