I have heard this questions several times, often followed up with “If testers can write good code, what do developers do then?”. This has prompted me to write this article.
I want to make the very clear distinction between ‘writing good code’ and ‘being a developer’. The two are not (or should not be!) mutually exclusive, but the former is something that anyone writing code should aspire to.
To know what good code is, you need to know ‘not good code’
What do I mean by good code? In my experience, look at most code written by testers as they cobble together test automation systems, and you will see many examples of ‘not good code’! I feel justified in saying that because I have written most of it in the past!
My ‘not good code’ worked, and on the face of it seemed logical, so as long as it works, what is wrong with it? Surely the end result is the important thing here so it doesn’t really matter what the code looks like as long as it achieves that? There is some truth to this, but over the past couple of years I have come to realise that test code must be held to the same high standards as product code; possibly even higher. You are expecting your test code to validate the function of your product, but how many times do you hear of ‘unreliable tests’ or ‘random failures’ or ‘resource leakage’ (eg not clearing up after themselves)? I’ve heard these terms throughout my 18 years of test automation experience.
Examples of ‘not good code’ (and notice I’m not using the term ‘bad code’ because it’s not, it just could be better!) might be:
– creating objects but not disposing of them
– having several ways to do the same operation
– using inappropriate data structures
– overly complex code to do simple tasks
– O(N2), O(N3) problems (or worse)
– related to the above, not considering performance (it’s only test code right?)
– poorly/inconsistently formatted code
– over use of inheritance
Some of these practices are laziness, but most are just lack of experience and lack of technical leadership. With only testers looking and maintaining test code, these sort of problems are inevitable.
So what is good code, and how do you get there?
In the past year, my coding skills have greatly improved. There is one reason for this, the developers got involved with the test code!
We had a set of test suites that were pretty good, but were a bit unstable. They had perhaps 90% pass rate (on a good day, with the wind behind them), but we knew what the ‘normal’ failures were (databases losing connection, network issues, random failures) and could quickly explain them away.
We had a team-wide push to fix the tests a while back, and this led to some very interesting developments. Firstly, the developers looked at the code and there were mutterings of “wtf?”; referring to the list of practices early in this article, we had pretty much all of them. The developers had to get us (testers) to walk them through the code. ‘Good’ code should be readable and self-documenting. Ours was not.
Our tests relied on creating databases for tests, yet we had a very rudimentary and laborious way of cleaning them up. This led to a build up of databases on the servers that although they were VM’s and rolled back to a clean state every night, were getting slow during the course of a test run due to the number of leaked databases.
Our developers started to come up with ideas for improving the code. One of the first things they did was to make our database object disposable, and set it to drop the database on dispose. How simple was that! Immediately, our database leakage problem more-or-less went away! Lesson learnt.
They then (pairing with testers), set about identifying patterns in the test code, and rationalising the code paths. So some of the operations that had about 3-4 different methods doing the same thing, now went through the same method. It sounds obvious, and it wasn’t our intention to get into that state, it just sorta happened!
They identified a few undesirable practices such as heavily overloaded methods/constructors, reliance on knowledge to use the right class for particular operations that was largely resolved by creating one or two factory methods that just made the test code so much more readable, and easier to write.
Just refactoring right?
Well essentially, yes. There was a bit of restructuring, but mainly it was refactoring existing code to apply these few identified patterns in a deliberate and efficient way.
However, the result was astounding. Now that most of the code is going through the same code paths to do stuff, those code paths can be optimised in one place rather than half a dozen. Now that more objects are disposable, the test writer doesn’t have to remember to clean up at the end of the test.
Result: Our tests are green 99.9% of the time. We still have a couple of ‘random’ failures, but rest-assured that we are looking at these (but these are the 0.1% that takes 99.9% of the time!).
But the developers did all the work, so how does that make testers code better?
Wrong, the developers didn’t do all the work. They were instrumental in identifying the patterns, devising the solutions to the problems, and generally enforcing coding standards, but the testers did alot of the work to implement them. Well actually (not to annoy the developers involved) it was a very shared job. Often, we would dev-test pair on the work.
The upshot of this is that we, as testers, now have a much better grounding in good coding standards. We know more about what the ‘right’ way to code is. Our developers set us a good example, and we review each others code before check-in, which further reinforces the fact that we can’t get away with shit/sloppy test code any more, any neither would we want to.
But we wouldn’t have been able to get to this point without the specialist knowledge and expertise of our developers.
So, can testers write good code?
Absolutely, yes! Learn from your developers, pair with them, ask them for architecture advice, get them to look at your test code (aka light blue touch-paper and stand back ;-), treat your test code like a first-class citizen in the project on an equal footing with product code. Everyone on the team must be responsible for product code AND test code. Once this is the case, the rest will follow.