The Three Stages of Learning to Test
I’ve noticed three progressive stages that I’ve moved through in learning to write tests. They may not be the same for everyone, but I suspect there are some people out there who can relate. At any rate, the following stages have been my experience. By the way, I’m really talking about unit / integration testing, rather than functional testing, or (since those terms can be quite muddy) about testing done by programmers rather than testers.
Stage 1: Testing because you have to
To begin with, a lot of people write tests because their place of work says they have to. For others (like me) it may be because a lot of people online keep going on about it. In my case, this also lead me to attempt to introduce it as a standard at work, even though I was just starting out with it myself.
Often during this stage, testing can be a bit of a chore. Part of the reason may be because you don’t really (deeply) understand what you’re doing, and so you make a lot of mistakes. Your tests are just plain wrong. For example, you might mock too much or too little, making your tests fragile.
But this is a natural learning process to me. There’s certainly nothing wrong with adopting something simply because most of the industry says it’s a best practice, especially when you don’t know any better. Once you’ve mastered it yourself, you’re then in a position to decide whether to retain it, or to tweak the process. It’s much better to adopt something you don’t understand than to ignore it for the same reason.
Stage 2: Testing to spot regressions
Once you start writing tests in an automated and repeatable manner, one of the first benefits you notice is indication of regression. If you refactor a method, you can be sure it still behaves in the same way if you have some test coverage. Additionally, you might also get tests complaining if you make a change to one class, and it breaks another (assuming you haven’t mocked out too much, or have integration as well as unit tests).
This is where some sort of continuous integration starts to become important (i.e. running the tests frequently – preferably on every checkin), especially in teams with more than a handful of developers, because the tighter your feedback loop becomes, the easier it is to track down the cause of the issue.
Stage 3: Testing to see if your code works
I started programming around the age of 10. While it was obviously a long time before I did much professionally, I’ve still always needed to see if the code I’ve written actually works. Until a couple of years ago, I’d usually achieve this by somehow running the program. This makes sense for something simple, but anything more complex can lead to the need for complicated (and often ad-hoc) testing rigs, or large amounts of time trying to get the application into the desired state.
Nowadays I often simply write a test. It’s a subtle change, but my brain process is not “I should write a test because it’s a best practice”, or “I should write a test in case something breaks this later”, but “I should write a test to see if this bit of code actually does what I want it to do”. I guess it took a long time to break down those years of habitually trying to run the code to see if it worked.
This stage is really what most people call Test Driven Development. Some would argue that if you just learnt to do “test first”, and to always write tests, you’d skip straight to this point. I tend to think you can still be in stage 1 even if you’re writing your tests first, and it’s not always practical – or even necessary – to write tests for everything. There are a lot of people who get pretty fanatical when it comes to testing… I guess I’m a pragmaticist at heart.
Still, I’m sure a lot of people have gone straight to stage 3. Or progressed in a different order. Or have reached further stages of Testing Enlightenment that I’m not yet aware of. If so, I’d love to hear your experiences.