Stop Refactoring your Damn Tests
Posted at 22:05 on Wednesday, 21 July 2010
PROGRAMMING A Ruby developer writing about testing? And writing about how he thinks it should be done!? What a shocking turn of events!
Test code exists to achieve, most importantly, two things. Firstly, to serve as an automated, low-level quality assurance. It’s used to passively weed out bugs and performance bottlenecks in your actual implementation so your team can feel a little more confident in their release — and in the case of libraries, to make the users feel the same.
Secondly, perhaps as a happy side-effect of writing tests for QA purposes, test code provides a great way to familiarise outsiders with your code by providing concrete examples of usage that actually work. The sweet little syntatic sugar provided by testing libraries such as RSpec allows you to recite a short example of how to use your code, and what the expected outcome is. Take, for example, this quick assertion. With a glance, I know that an Account has a method money_owed which should return a Float.
@account = Account.new @account.money_owed.should == 1_213.94
For your test suite to work as a good QA robot and a friendly doormat, it needs to be:
- Working; lest you receive false negatives or positives when running it,
- Readable; lest the new guy on your team takes days instead of hours to acclimate himself with your test suite.
I’m sorry to say it, but these criteria can be easily broken by refactoring. As you are probably well aware; by the very nature of refactoring, generalising, and generally DRY-ing up any code you obscure what’s happening to some degree. As ugly and inefficient as it would be, you’ll understand much quicker when presented with 8 nearly-identical lines of code as opposed to a function that spits out other functions which then proceed to dynamically invoke a method based on a string fetched from an external environment-dependent configuration file.
And that’s good! Your implementation code should be clean, modular, readily extensible and with as little complexity as you can prevent. In many cases, like that of the nested if/else ladder that rolls 6-levels deep which gets transformed into a nice polymorphic block, refactoring can actually make things more readable.
So why am I railing on refactoring test code for killing readability? Well for one, your test suite is unlikely to have any branching conditions — that would kind of defeat the point. In order to do it’s job well, your test code needs to be simple, concise and definitely not complex. Sometimes in the process of adding functionality to your app your enthusiasm for making a great product can outrun your housekeeping ability, resulting in messy code — and that’s OK, you just return later and refactor it — but since there’s no new functionality being built into your test suites, there’s no real excuse for the code becoming a mess. There shouldn’t be anything to refactor.
No longer would your tests be prime examples of what should happen, but just a chance to show off how you can drop a class_eval to reuse these tests for other classes or modules — something that will require a lot of backtracing just to understand what your test suite is illustrating for a new developer — or maybe even yourself a few years down the line.
“OK, so I guess it can make my tests unreadable… but break them?”
You bet your ass it can break them. Your tests are most effective when they are applied against chunks of code that are a little more daring (for lack of a better adjective) than usual. Like the trapeze artist who prefers to leap through the air at the circus rather than sit in the stands munching popcorn (or some other analogy that doesn’t sound so dumb); good, maintainable code usually employs ‘daring’ techniques like long-winded inheritance trees and metaprogramming which could do with a bit of a safety net to warn you if your new, clean implementation isn’t behaving like it should. As such, you need to be able rely upon and trust your tests to do their job.
If you use the same ideas that you do to clean-up “real” code in your test code, doesn’t that tease the possibility of having to test your tests?
Instead, keep it high-level. Stick to the constructs provided to you by your choice of testing framework; chances are, they’ve already been tested themselves. My beloved RSpec, for instance, exposes some extensions to the lambda which enable you to assert changes in state as a result of a block of code:
@account = Account.new(:balance => 1250) lambda { @account.deposit(3000) @account.give_dividend(2500) @account.withdraw(500) }.should change(@account, :balance).by(5000)
The next time you find yourself writing test code, take a step back for a minute and decide if you’re going too far. If there is even a possibility that your tests could warrant their own test suite, I think you know what you need to do. If ever there was a code smell to trump all code smells, that would be it.
So please; for me, your team and for yourself, stop refactoring your tests to the Nth degree. Remember the two main reasons for which they exist in the first place — keep them readable, keep them reliable and let them do their job.
And above all else, test all the fucking time.
Tagged as: programming, rant, ruby, testing, bdd, rspec, refactoring

I had to convert a whole bunch of typical VIDEO_TS folders to watchable-on-a-



