I always read this... What exactly is a unit test? What parts of my software should be tested? What are some examples of good unit tests and the code that is tested?
I always read this... What exactly is a unit test? What parts of my software should be tested? What are some examples of good unit tests and the code that is tested?
What parts should be tested? Everything that can be conveniently tested, and some distance past that. The goal is "every externally visible behavior of a class"; that is, everything that matters to a caller. For example, let's suppose I have a "sorted container" class. I'd want to test that it returns stuff in sorted order, maybe with two or three items, and with lots (hundreds or thousands). I'd want to test that it handled duplicates correctly. I'd want to test that it worked correctly with only one item, and with zero. I would not want to test whether it was internally implemented with a vector - that's not something that the caller cares about. So if the sorted container is changed from a vector to a tree, the unit tests should run unchanged.
Just last week, I was breaking apart a singleton into three related singletons. "Related" means that they each need a reference (or pointer) to at least some of the others. Turns out I coded a recursive constructor loop. A unit test pointed that out to me.
I've seen unit tests catch race conditions. Catch that I was calling a pure virtual method in a base class destructor. Plus of course all a whole bunch of the usual regular bugs.
The easiest examples of unit tests are math functions, since they're so well-behaved.
Here is a square root function in Javascript:
function sqrt(n) {
guess = n/2;
while(Math.abs(guess*guess - n) < 0.00001) {
guess = (guess + n/guess)/2;
}
return n;
}
assert_equals(4.0, sqrt(16));
There is nothing to set up because this code doesn't depend on a database, or an HTML input field, or the phase of the moon.The call to assert_equals there is a unit test. (Also, keep in mind that assert_equals is a glorified if statement)
"well, what isn't a unit test??"
Since we can call anything we want a unit, the answer is "whenever we say we aren't doing a unit test". But a more practical answer is: whenever you're testing the interaction between two or more units (where a unit is a function/method).
If you're making a video game, you might have enemies who can jump, and guns that can fire. It would be a unit test to check if enemies jump correctly, and it would be a unit test to see if guns fire correctly. It wouldn't be a unit test to see if guns fire correctly from a jumping enemy, since this is the combination of two pieces.
Another way to (sort of) define a unit test is to say "how does this behave assuming that the rest of the system is correct"? That is, we aim to check the behavior of our code in isolation from the rest of the system.
This is important when something is wrong, because if we know what is behaving correctly, we don't have to waste time checking if it's the cause.
Why does that matter? It means that you're writing a user of your class before you write the class. That means that the interface of the class gets designed from the mindset of a user, not an implementor.
More: The interface gets designed by someone who wants to be able to test all the externally-visible behavior of the class. If you can't test it, you have to think about re-designing the class interface - often by breaking it up into smaller classes. I've seen this in practice; the net effect of this is better class design (plus thorough tests).
That said, I'm considerably stronger of a proponent of tests than I am of TDD. If you don't like the TDD approach, still write tests. Write lots of them. They'll often save you when you make subtle mistakes in your next set of changes. (Nothing like fixing a bug, running the tests, and getting informed of all the implications of your change that you forgot about.)
> That means that the interface of the class gets designed from the mindset of a user, not an implementor.
From my experience, something entirely different happens. The class gets designed from the mindset of a third party - the tester. Which is, I believe, a mindset different from the user. If all you're testing is the externally-visible behaviour, fine. You're pretty much treating the class like a library. But if you start injecting stuff into the class to test if some other dependent services got called properly, etc. - and especially if you start designing the interface around such testability, then I believe it'll lead to bad, unreadable code. In particular, I believe adding complexity to the class for the sole purpose of making it easier to test is a code stink.
TDD taken to the extreme prescribes that you should only ever write dumbest possible code that makes current tests pass. If you need more complicated behaviour, you first have to write tests for it. But this gets quickly out of hand if your project is meant to do anything more complicated than being a simple CRUD layer, because test complexity rises in lockstep with production code complexity. I've seen cases when people blindly following the test->code->refactor cycle created tests that themselves were isomorphic to the algorithm they were implementing, which makes one ask where did they have the code that tested if tests themselves are implemented correctly?
So personally, while I like tests (particularly regression tests), I just can't make myself follow TDD.
> But this gets quickly out of hand if your project is meant to do anything more complicated than being a simple CRUD layer...
I've done things a lot more complicated than a simple CRUD layer. TDD held up just fine.
> I've seen cases when people blindly following the test->code->refactor cycle created tests that themselves were isomorphic to the algorithm they were implementing...
Well, blindly following any methodology is likely to get you in trouble, one way or another. The problem is blindly following, not that they're following TDD.
That said, I did TDD, and now I don't. It worked well for me, I loved the kind of code that came out of it, and I still don't do it any more.
You should have thoughtful integration tests. Unit tests are only useful when you have an obvious independent "unit" with semantics that are completely independent from the rest of your code. If so, you test those.
If you want to make sure your functions work, write unit tests.
If you want to make sure your application works, write integration tests.