I’ve given you 3 Reasons Why It’s Important to Refactor Tests. But what’s actually involved in refactoring tests?
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.
The definitive book on this topic is xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros. It’s a big book, full of patterns and smells. But there are 3 simple steps I take most often.
At the top is a five-minute screencast where I’m refactoring tests in my iOS TDD sample app using those 3 steps. My example is in Objective-C, but the principles apply to Swift (and beyond).
Step 1: Always Start from Green
Never refactor anything when tests are failing. This applies to refactoring production code, and to refactoring tests.
In any refactoring, start from green (tests passing) and end in green. Otherwise, you’re not refactoring.
Step 2: Extract What’s Common into the Test Fixture
Some objects are common across tests, so extract them into the test fixture. I take one variable at a time and extract it to a property. (In Objective-C, I extract to instance variables instead of properties.)
Then I move the creation of the object into the setUp method.
These days, I also set it to nil in tearDown. This is especially important for the System Under Test (SUT) because it tests the full lifecycle of the SUT.
But since making this video back in 2016, I no longer ask myself which properties to tear down. If you set it up, tear it down.
(I quickly create well-formed setUp and tearDown using templates. You can get them here.)
Step 3: Extract What’s Not Common into Helper Methods
What do you do with configurations that are common across some tests, but not all tests? Don’t create them in setUp. Instead, extract them to small helper methods.
In the screencast, you can see I extract a dummyRequestModel helper method. Choosing good method names makes tests more readable. A good test expresses what is important, and hides what is unimportant.
A good test expresses what is important, and hides what is unimportant.
Extracting helper methods applies to all three phases of a test: Arrange, Act, Assert. But be careful with Assert helpers. See how to make custom assertions in Swift.
(Objective-C requires even more care. You want test failures to report the line number of the location in the test, not the location in the helper. You can do this with preprocessor macros. But there’s a more flexible and reusable approach: define your own custom OCHamcrest matcher.)
Take a moment and look over some of your unit tests. Can you find anything to extract to the test fixture? Is there anything you can extract to helper methods?
I did this exercise myself. My eyes were drawn to any tests with repeated concepts. They’re pretty easy to spot, since these tests are positioned close to each other. I looked for repeated objects and repeated code. I paid close attention to any Arrange section with more than 3 lines.
My tests are now easier to read than before. And it was fun!
I also did the Bowling Game TDD Kata recently. It reminded that anything that has comments ought to be extracted. This is true even if it’s used in only one test. So look for repeated test code, but also watch out for any comments.
Don’t forget to run tests before and after refactoring!
Did you discover anything to clean up in your tests? Do you have any questions about test refactoring? Leave a comment below.