I’ve ranted in the past about the general uselessness of unit testing of code.

Ok, before you get all up in my grill about this, let me be clear about this. I think testing is useful. I think unit tests for particularly strange functions are useful. If you can fully specify a business requirement in a unit test it’s likely useful.

I happened on a paper about exactly this the other day. I was quite giddy to have someone be able to state my complaints with a far better way then I can.

Go read the paper yourself.

Basically the crux of Mr Coplien is that testing that doesn’t add value actually subtracts value. A test that can’t fail will always pass — the pass doesn’t add any value.

An example of such a test is, at its simplest, something like this:


void setToFive() {
    value = 5;
}

You would then write a test that looks like


setToFive();
assert(5, value);

The TDD advocate would say that you wrote these in the other order. Sure… so what. You’re testing that the compiler can generate code that will set value to 5.

This isn’t a business rule. This is a simple restatement of the method under test.

You might have a test case that looks like this (for a different function):

numberOfTheCounting = getNumberOfTheCounting();
assert(3, value);
// "Three shall be the number thou shalt count,
// and the number of the counting shall be three."
// See the sacred Book of Armaments

At least this is taking a business rule (or would that be a Python rule? Whatever…) and putting it into the test. If God came down and changed the number of the counting then you change the test and fix the code. Ok.

It was no longer possible to reason about the execution context of a line of code in terms of the lines that precede and follow it in execution … That sequence transition now took place across a polymorphic function call — a hyper-galactic GOTO. But if all you’re concerned about is branch coverage, it doesn’t matter.

The real problem that happens is when you take a well thought out algorithm and break it into a bunch of “easily tested methods” purely to bow down before the god of code coverage. What used to be code that read easily can be transformed into a spaghetti of classes, interfaces, factory methods, mock objects, and so forth. The clarity of the code has been fundamentally compromised. Code, after all, is written not for the computer or even the compiler but for the people that interact with it now and in the future.

Mr Coplien had an interesting proposal — any unit test that hasn’t failed for a year should be deleted. It’s simply code that you are carrying around to make you feel better. While it makes you feel better in some abstract way, it also costs concrete time on a day-to-day basis as you need to both run and maintain the test. A test that never fails never adds value. The tests that aren’t breaking (which, let’s be honest are most of them) simply prevent changes to the code under test and provide an impediment for the system to grow forward.

The other observation made is that trying to get code coverage does not ensure correctness or lack of bugs. Even if you have 100% code coverage, you’ve never tested it with all of the universe of input data that you can encounter in real life. You get the green bar of tests passing without getting any better certainty that your system will work.

The take-away is you should spend your marginal time writing integration tests rather than unit tests. Integration tests do give you confidence — some confidence at least — that your system is working designed. And after all, that’s what really matters.