Unit Tests Part 1: Why You Need It
I intend on putting together some information here about why unit testing is helpful, when to do it, and how to do it (at least, how I’m doing it in my XNA game).
I’m not going to write it all at once. This is just the first part of the story.
I want to start by introducing what unit tests are, at a conceptual level, and why people do them. If you’ve been a programmer at a company that has the software act together, you’ve seen unit tests. Most universities that you could attend would also give you an introduction to them. But if you’ve been learning how to program on your own, you may not know what unit testing is, or what it’s good for.
First of all, let me start off by saying I’m not a unit test fanatic. Nor do I necessarily subscribe to the idea of Test Driven Development (TDD) where you write all of your unit tests for your code and you’re done writing code as soon as the tests all pass. But I do think there is a time and a place for it in nearly every project that you’ll ever write.
So what is a unit test?
It’s a test for a small unit of your program. (I’m sure that caused your brain to invert itself, and now you see smells and taste sounds, right?)
Basically, when you write a method, you write a second method (or even multiple methods) that do nothing but call the first method, passing in specific values, and making sure that the returned results match with what you know they should be, based on the values you passed in. A concrete example will help…
I just got done writing code to check whether, and where, a ray intersects a plane. I’ll be using this all over the place in my code as a building block for bigger things. But I’ve got the code written, and I’m fairly confident that it’s correct. But I’m not 100% positive. So… what do I do now?
The rookie approach is to try it out. You run your program and see if it looks like it’s doing what it’s supposed to. In the rookie approach, if you don’t yet have something that will show whether this piece is working or not yet, you wait until there is. The rookie approach works for some things.
But the better thing to do is to write unit tests for it. I’m concerned about my plane/ray intersection, so I go elsewhere and write unit testing code. (Technically, I’ve got my unit tests in a completely different project than my original code so that it doesn’t get compiled and shipped to the user. They don’t need it. I’ll cover the specifics another time, but for the record, I’m currently using NUnit 2.6.2.) In this other place, I write a method that passes in a specific plane and a specific ray that I can do the calculation for in my head. For instance, I know the xy-plane should intersect a ray going along the z-axis right at the origin. So my unit test code would create a new plane that matches up with the xy-plane, a ray that goes along the z-axis, and call the ray/plane intersection code directly. When I get the results back, I check to ensure that it’s <0, 0, 0>, as I expect. If it is, the unit test passes. If not, it fails, and I’ve learned there’s something wrong with my code.
Just as important as making sure your code is working correctly the first time, is that unit tests are an insurance policy against change. Let’s say you’ve decided to revamp the code for doing ray/plane intersections. Perhaps you’ve learned you need a faster approach, and you rewrite it from scratch, or change a part of the existing code. Your old unit tests should still pass (assuming you only changed the way the insides of the method worked). If you broke something, you’ll know about it immediately, or at least as soon as you run your unit tests again.
Even though it takes some time up front, having unit tests will make your life much easier in the long run. I’m not saying you necessarily need to unit test everything you ever write. (More to come on that later.) Just that a project with more than a couple hundred lines is probably going to need unit tests at some point.
There’s a lot more to this subject that I’d like to cover (like how to actually write unit tests) but I’ll save all of that for another day. Remind me if I forget.