The Programmer's Testing Trap
"If you wish to build a ship, do not divide the men into teams and send them to the forest to cut wood. Instead, teach them to long for the vast and endless sea."—Antoine de Saint-Exupéry
As a young fledging programmer, I was told (and had a hunch) that testing is important. Yet, I never really knew why testing was important. At university, we were made to test everything. We were, proverbially, sent into the forest to cut as much wood as we possibly could.
My tests broke builds more than my actual code did.
Particularly whenever I wanted to change my code. The change worked perfectly, but I was still testing for the original code, and so the tests made loud angry noises that indicated what sort of awful human being I was.
I stopped writing tests.
Things were much easier, I could do what I liked. Nobody said no. However, when the time came to refactor a major component of a fairly large project, I realised why tests exist. Each time I made one change, I had to manually go through the whole program, making sure everything else still worked fine. I realised that, actually, I was still testing. Not with code, but I became my own test suite. I was using my own time and eyes to test what could have been automated.
I started longing for the vast and endless sea.
My breakthrough came from the book Practical Object Oriented Design in Ruby (can't recommend highly enough). We should test interfaces and behaviour. Not data structures, not methodology.
Let's consider the simple interface of "I want pizza". We have two objects involved: you, and the pizza shop. You tell the pizza shop "I want pizza". In this situation, what should be tested?
- The number of staff in the shop?
- The order in which ingredients are added to the pizza?
- Whether the delivery man uses a scooter or a car?
Actually, the only thing that should be tested is whether or not you receive a pizza. Think about it. If you didn't know anything about the process, yet you still receive a pizza, is the interface successful? Yes, yes it is.
By testing only the interface, the question and its result, we now have flexibility. The pizza shop can change their staff, and the test is still happy, because the pizza still arrives. The delivery man can win the lottery and deliver your pizza in a Jaguar, and your test doesn't complain, because you still got the pizza.
The test still gives you security—any change that will actually stop you getting a pizza will break the test. The shop can change their ingredients, as long as they are still giving you pizza. If they make roast turkey instead, then "I want pizza" is a failed interface, and your test will break.
So let's make sure we are testing, and testing well. Let's test interface, instead of implementation. Let's test the "what", not the "how". These tests no longer hinder change and improvement, but they do ensure the successful operation of your code.