« Comparing Collections | Main | The day I got the metaphor »

The Irksome Power of Ignorance

Listen to this articleListen to this article

"Scarcely any degree of judgment is sufficient to restrain the imagination from magnifying that on which it is long detained" -- Samuel Johnson

So "why then do I need all these unit tests for my code?" I read with amusement. Why can't I just write functional/acceptance/integration tests and be done with it? The arguments are ludicrous. Saying that there are people we know who obsess about unit testing yet seem to write crap software and/or go overboard with seemingly useless tests in no way proves that it's a flawed concept. Holy crap! if we applied that kind of logic to the entire industry we'd have stopped doing software development years ago.

We have to be very careful what we think we're arguing about. I'd probably get upset if a Windoze weenie told me that Java was slow when what they really mean is that Java seems slow on Windoze! It may well be the case that Java is slow. But the fact that it's slow on Windoze hardly justifies saying that Java as a whole is slow.

The fact remains that what most people do IS integration and functional testing and therefore most people know very well how to do this. Just because most people don't know how to do proper unit-testing doesn't mean unit-testing is wrong. It just means we work in a very immature field. Hell most people (myself included) don't know how to do good software design. But we're getting better at it!

Unit testing has proved itself time and time again in other fields. Do you think Ferrari needs an alternator to prove is engine design? Does the alternator manufacturer need an engine to prove its design? No. Likewise, every component (unit) of your computer has been individually tested before it's placed into the PC your using to read this. Every single one. And then some! The components that make up the hard disk were individually tested before being used to assemble the hard disk. But does the PC manufacturer care about the components inside the hard disk? No. Of course not. Why? Because to them the hard disk is the smallest unit.

And so it is for software. Each "layer" in a system can be thought of as both a higher level of abstraction than the layer below it, yet a lower level of abstraction than the layer above. Sounds obvious right? Because software is and should be fractal in nature, it looks essentially the same no matter the magnification or level of granularity.

Do you test whether the JVM can actually increment a variable using the pre-increment operation? No! Do you test that the String class behaves correctly? Do you test to see if "your favourite framework" does its job? Probably not. At best you can safely believe it works as advertised. At worst, you write some integration tests to prove (or not) that your understanding of the tool matches reality.

Go have a quick search on Google and see what types of testing go on in electronic, automative, aerospace and just about every other field of real engineering I can think of. Not this trumped up Research and Development we like to call Software Engineering. Here's a completely random start.

So when it comes down to it, the same people that tell me that "the network will never be transparent" because if I'm building a nuclear power plant, I can't afford to just "ignore" network failures, also want me to believe that I should overlook a development and testing regime that has proven itself time and time again in other, more rigourous, disciplines!

The project I'm on has 1200+ unit-tests that take around 11 seconds to run. Then we have a smaller yet substantial number of end-to-end regression tests, an even smaller set of end-to-end acceptance tests for each iteration and a tiny number of integration tests with external systems. So maybe we're not your beloved "Thought Leaders" but we definitely have plenty of experience doing other than unit-testing.

We have zero bugs that we know of. We have things the users don't like because they changed there mind or the developer didn't understand the requirement. But we have rarely, if ever, encounter unexpected behaviour in the system. When new code is added, it quite clearly breaks the build if the developer has made a mistake. I can't tell you how many times it's saved my ass. We've definitely not encountered the types of problems usually associated with complex systems whereby strange interactions between objects causes software failure. I put this down to well defined and tested interfaces, whether they be "artifical" or not.

We perform around 20 or so automated integration builds a day running the full suite of tests and each developer runs the unit tests countless times each day.

The best part is, our system is nicely de-coupled. We can quite happily replace bits of our system with little or no impact because the unit-tests defined our interfaces.

Oh and as for writing bazillions of lines of code, actually my experience is that we end up with less code. In fact I find myself deleting more code everyday as we refine our abstractions, usually driven by testability. But that's just my experience.

I know the software I produce today is, at best, average but the average has gone up since I started working - at least in the product development (as oppsoed to consulting) circles I keep. And it will continue to go up as we mature as an industry.

TrackBack

Listed below are links to weblogs that reference The Irksome Power of Ignorance:

» Automatisointia, joukkue from Confluence: Oikotie
Tällä viikolla on mun homma tehdä aamun rutiiniduuneja ja eilen ja toissapäivänä niissä paljastui tai muuten vain tuli sen verran verkkoongelma ja muuta selviteltävää, että "tuottava työ" jäi aika vähälle...... [Read More]

Comments

a truer word was never spoke. no, wait, it probably was. but i definitely agree with this.

Good blog entry, but I have a few comments. First, in my blog entry you cited I'm not trying to rant against unit tests. What I'm criticizing is an obsession with unit tests almost to the exclusion of other testing types, and the belief that the lower-level your tests are the better. Eckel's idea about embedding tests as comments in main-line code show that he's gotten to the point that the tail is wagging the dog, and he's forgotten the point of software development.

Secondly - alot of your article is comparing testing/QA in the manufacturing field vs. software testing, and I believe this is largely a mistake. Hardware testing needs to be done potentially millions of times over and over again - once for each thing rolling off the line. Software isn't like that - once the code's created you don't have to "manufacture" millions of copies of it :-). And hardware or physical goods are relatively static beasts compared to software. Software moves fast and isn't "manufactured", and hence its testing necessarily has to take these points into account. It sounds like your system has struck a nice balance, congrats. My blog entry was all about people who have skewed the balance towards low-level unit tests to the exclusion of almost all things - a rather different environment/design than what you described.

Hey Mike. Thanks for commenting. Really appreciate it. I was going to reply to your entry but my rant wasn't specifically about what you had to write it just made me think about it.

I guess I was trying to say that it's not necessarily a bad thing but, as the quote at the start hopefully suggested, obsession is not always such a good thing either and that, IMHO, ignorance and in-experience are largely to blame.

I agree in part about the manufacturing comparison too however, to me, the "manufacturing" process is really the compilation and deployment, not the copying. In an agile environment such as ours, that level of automated QA is paramount. I also dislike the comparisons made with the building and construction industry as a close relative is "real" architect and their industry is as "bad" as ours :-).

And as for Bruce Eckel's stuff, I'm in agreement 100%. It's one of the most crack-headed ideas I've seen in quite some time and, unfortunately, gives the TDD crowd a bad name for all the wrong reasons.

In the end, there is no substitute for good design and experience no matter how that may come about.

Cheers,

Simon

You sort of hit on what really motivated the article. A number of what are increasingly being called "Thought Leaders" are taking several good ideas and beating them so much to death that they're weakening the fundamental underlying arguments - and throwing people's views of software development, particularly people just learning, seriously out of whack.

I mean, it's kind of hard to argue against Unit Testing - as part of an overall development and testing strategy such as you outline. But Eckel, and others, are blowing things like Unit Testing _way_ out of proportion and are quite literally showing us how not to create software, at least given their examples of how to use these techniques. For example, Eckel never comes out and says as much, but the message if you read all of his stuff is "functional & integration testing are stupid and a waste of time - focus on Unit testing". As I said - he doesn't say it, but it's all he talks about, and if you look at his code and others there's almost no hint of any higher level tests.

The end result will be yet another backlash in a year or two - people rebelling against unit testing because they'll look at Eckel-inspired projects with 50,000 lines of too-low-level unit tests against 5,000 lines of code and no other testing strategy in place, and some joker (maybe even Eckel -these guys work in cycles) writing a book denouncing the evils of unit tests. Just like how the OO books from the early 90's onward kept changing their tune to the latest rage.

I'd like to behead the guys that wrote the Core J2EE Patterns book or for that matter the entire pattern movement for all the evil it causes in developers who blindly apply them with little regard to the underlying motivation. That doesn't mean I don't like say the original GoF patterns or apply them on a daily basis. What I do say is that, unfortunately, very little innovation has occurred since that thinking - most "new" patterns are really compositions (no pun intended) of other patterns.

Everyone these days seems to do some kind software development. We continue to believe that S/D is some kind of communist utopia where everyone is equal and everyone has the "right" to create commercial software. To me that's equivalent to saying everyone has the right to own a gun.

So maybe the real issue you percieve is one of responsibility? Should we allow someone who is considered a "Thought Leader" to publicly push an idea or concept (no matter hpw crack-headed it seems) as far as they can just to see what's possible or should we stifle innovation by "protecting" developers from themselves (and others)?

Whatever the answer, unit-testing as a concept and as a practice is not to blame. Though, if it does prove to be a dangerous thing for the majority of developers, then the government may have to step in a legislate against its use :-)

The main problem I have with the manufacturing example is that the vendor of the chips in the hard drive intends them to be general purpose reusable chips.
There's a set of specifications that it needs to comply to, and they test for that.

Most of the stuff we write doesn't need that. It's intended to work in this project, and if it does that job then it does what I need it to do.

Maybe it doesn't work in isolation, but if it work in integration, then how is the client any worse off?

(It's rare that something could work in integration if it doesn't work in isolation, but it does happen)

True but isn't the idea of interfaces and contracts that a component will work as advertised? If class A depends on class B, you don't usually "write" the contract as B will work in this way if and only if called by A.

The idea is to manage complexity. If we can break the problem down into manageable chunks of responsibility, surely that makes building and understanding the system much easier. The trick, as Mike originally points out, is to work out what the appropriate level of granularity is.

Simon, yep. For my own work, my definition of "unit" is much higher level than what you commonly see advocated by Fowler, Feathers, et al. My "unit" is typically a component visible to an external user, with all of the machinery that goes on behind it coming along for the ride.

This sort of unit isn't concrete and fixed as it traditionally is - usually, it's a class and a bunch of methods that serve as your unit. For me, a unit is conceptual - something that conceptually can stand alone, or mostly stand-alone. This may be one class, or several classes, or an entire package or sub-system.

I also advocate layers of this sort of unit testing - so that at some point as you move upwards in abstraction you start blurring the line between a functional test and a unit test :-)

As a final note - unit testing is utterly worthless for at least 50% of my code, since I work heavily with multithreading and networking. So much of my own testing is functional, stress, and load testing to see that the code performs correctly under real life conditions.

Post a comment