Getting Legacy Code Under Tests
Unit testing legacy code, especially around data access, can be hard, so I thought I’d share a technique that’s worked for me.
FreshBooks’ code base has been around for a long time. As with most code bases like this there are certain parts of the application where we are dealing with “legacy code” in every sense of the term. The developers at FreshBooks are committed to improving any code we touch and when working in the “legacy” sections of code this can be really tricky. Some areas are big giant PHP files with no classes (sometimes not even functions) laced with SQL queries directly accessing the database. These sections will usually have integration tests around the larger functionality the file provides but no unit tests around the smaller assumptions in the file.
Before I go make changes to a file like this I like to get some unit tests around these smaller assumptions so I can confirm they stay the same or, if they need to change, I can be explicit about how. Here I’ll present one technique I use for this.
The first thing I’ll do is identify the chunk of logic I want to get under test. I’m going to completely make up an example here. Let’s say we have this code:
Fairly clear. However imagine this code is inside fridge.php which is responsible for figuring out a lot of stuff about the fridge and this snippet above is surrounded by hundreds of lines of code. With code all around it, and all the SQL calls happening in line, making a change to the logic would be really scary. Nothing is keeping this logic contained. What if someone accidentally moved the $buyMoreBeer = false below the rest of this logic? Then we’d never buy beer and developers would start dropping dead from dehydration left and right! So what do we do?
First, we need to get this logic pulled out and contained so we can wrap some tests around it. We can do this by extracting it into a class.
Start with a class that has a single method containing the above logic that returns the value of $buyMoreBeer:
Now the code back in fridge.php can be replaced with:
Ok, so not much win there. Even with the logic pulled into a class it is going to be hard to properly unit test with those two queries there. So the next thing we need to do is pull those queries into some protected methods:
Well now that is looking much more clear. And now we are all set to get it under some tests. Notice how the two methods containing the SQL calls are protected? The reason we did this is so we can make a testable version of this class. So usually right within my unit test file for this functionality I will declare a testable version of the class. It’ll look something like this:
By overriding the two protected functions we basically now have a class where we can control what is usually returned by the database and can focus completely on testing the logic in the shouldPurchase method.
So finally we can create some tests.. they might look something like this:
And now all the assumptions of logic about when we should buy beer are under test and if anyone changes those assumptions, our tests will let us know. Beer will continue to be bought and the world is a better place. I hope you found this helpful!