My Thoughts on Unit Testing
Posted on Apr 16, 2011 in Programming
I've done it again - skipped a week. Final Fantasy XIII has a habit of shortening the length of my day. No idea how. So I have no choice but to write in a slightly "bloggier" style, because one of my usual articles will take me too long! Anyway, let's ramble on about programming for a bit.
Often I think about why I work on my own programming projects. I'm not paid for the work, and it's likely that they'll never actually get a release anyway. The reason is pretty straight-forward: experience. Same answer as any other passionate programmer, I'd expect.
Many people try to follow the 'learn one language per year' technique to gain experience. I find myself unable to do this - I consider myself a graphical tools programmer, which means that I tend to see things from a different perspective to most. Language is mostly irrelevant; the important experience comes from being able to use program structure in interesting ways. In that way, some of the most valuable experience in my eyes is abstraction away from low-level concepts.
[That said, I think it's important to learn an example of the big three language types: procedural, object-oriented, and functional. I'm yet to learn a functional language, but it's on the list. And yes, I do realise that means I'm being a bit hypocritical.]
I tend to learn something by doing it. This applies to programming just as much as anything else. In this case, I choose to gain experience by programming. My languages of choice are PHP and C#, though the languages used are not really all that important for the purposes of this post.
I keep a 'to do' list of programming concepts, libraries, frameworks, patterns, and so on that I wish to learn. Then, when the time comes, I learn them by implementing these concepts in my own projects. Some examples of recent (and not-so-recent) things that I've crossed off this list are: MVC, OpenGL, Mediators, MVVM, and Lambdas.
There's more, but that's a good list of examples. I never really completely learn these subjects when I cross them off my 'to do' list - I simply have a good basic understanding of the concepts and have used it at least once in a working application. If the time comes that I need to use one of these concepts, I am then prepared to use it immediately, while at the same time, I am able to fill in the gaps in my knowledge while I write my code.
So how does this tie into this article's subject, unit testing? Well, unit testing is one of those things that hangs around on my 'to do' list, but never actually gets done. On my list, Unit testing appears next to a list of related concepts such as Dependency injection and Mock objects.
Like a bunch of things, I understand the basic concepts of these, but lack motivation to learn more about them until I need them. And up to this point in my life, I haven't needed them. I know that a lot of extremely talented programmers wouldn't want to associate themselves with someone who says something like that, but I have not (properly) used test cases in any project so far.
Don't get me wrong, I understand why unit tests should be considered important. What I think is more important is the fact that unit tests are not appropriate in some cases. And this is why I mentioned my personal hobby projects.
If I write the test before the code, I am basically repeating my logic a second time when I write the code. The test will pass because I wrote both the test and the implementation. This works the other way around, too - when I write the implementation first, the test will simply be me going through the same steps as before.
Which brings me to my first point: The only time I see a point in writing unit tests in any single-programmer project is when writing algorithms. An algorithm, by definition, has an input and a desired output. Not only is it easy to test something like that, it's critical to test the output to ensure that the algorithm is indeed working as expected.
I can spend a few paragraphs explaining what I mean by 'algorithm', but I am talking about reasonably complex data operations, not simple stuff. For example, it would make sense to write a set of unit tests for a sorting algorithm, but it wouldn't make sense to write a set of tests for your data access layer. Why not, you ask? Because if my data layer was broken, my application would not work! Why bother testing something so blindly obvious?
Right. Well, this is all well and good when talking about a project with one programmer, but what if there are two or more? There is more to consider in this case, but I'll start with this: Unit tests are not important at all if your code is simple and straight forward. You can write tests for a simple function until your fingers fall off, or could just look at the code and easily identify any mistakes. If your code is not simple, then it is either an algorithm (to which the exception above still applies), or the code is not written well.
I, of all people, know bad code quite well. I write plenty of it myself, and I find myself fixing plenty of it at work, too. In this way, forced unit tests will make programmers reconsider their bad code and restructure it for easy testing. But in this case, testing is simply the cheese to lure out the real goal: simple code. Instead of wasting time writing unit tests, I think It would be better if the programmer simply stopped and structured the code correctly in the first place. No unit tests needed, but the code is still simple. Everyone wins!
There are, however, a few cases when unit tests may be very, very helpful. If multiple programmers are going to be working on the same code at the same time, unit tests provide a simple way to ensure that behaviour remains the same between multiple edits from different users. Unit tests also help the programmer who is new to an established code base or language. By passing the tests (assuming they are complete), a newbie can be assured that their edits are valid and working.
Finally, unit tests are excellent if you are rewriting a bunch of code, and need to ensure that your rewrite follows the same rules as the original version. But this is uncommon, and if you are rewriting such a large chunk of code, it is likely that the unit tests would need to be rewritten as well. But if this is not the case, then you can quickly write up a few test cases on the original version, then rip it out and rewrite it before running the tests again. It is a good way to ensure that your refactoring is working right.
These are just my experiences, and I know that everyone has a different opinion on testing. Personally, I see it as a useful tool in some situations, but not a trophy to carry around with you and rub into everybody's noses, like many seem to think. That said, I have never worked in an environment that pushes unit testing on you, so I may be completely off-base. Which isn't rare.
