I have frequently heard this complaint, from some really great engineers in the past year. My code’s untestable, there’s no way i can test this, the only way to test my code is to write a full on end to end test. And in some cases, it was actually true. But the thing is, it doesn’t need to be that way. There are always ways to twist and turn the untestable code into testable code, in other words refactor.
But before you get excited, and go aha, thats what I am going to do, hold your horses for a second. What are you going to go refactor ? Well, don’t scratch your heads, there are a few things to look out for. Similar to the code smells we had, there are similar smells we can look for, which indicate that the code is untestable. And there are a few standard ways to tackle them, and make your classes testable. So without further adieu, lets take a look at the more common and annoying testability smells.
- Constructor doing work :
This is one of the biggest things preventing a class from being testable. There are many names to this smell, including constructor doing work, Breaking the law of demeter, etc. but all it comes down to is the constructor doing more than just assigning stuff to local variables. For example, something like :XPathConvertor() {
this.xpathDatabase = XPathDatabaseFactory.getDatabase();
XPathMapper mapper = new SimpleXPathMapper(”Simple Mapper”);
this.xpathTranslator = XPathTranslatorFactory.getTranslator(this.xpathDatabase,
mapper);
} -
While the above may seem a contrived example, or too simple, it exhibits what is at the centre of most bad constructors. One, its a default constructor. Then it goes out, grabs a database out from ether (Static factory method call), and then creates a mapper, and then passes those two to get a XpathTranslator object. Now, take my word for it, but the XPathConvertor only needs xpathTranslator. SO what is it doing with the darn database and the mapper. This is breaking the law of demeter, which states that “The constructor should only ask for what it needs, and nothing else.”
Why is this bad ? Well, for one, if the thing your constructor is creating is a heavy piece of service like a database or something, there’s a huge hit in your test. Your test is no longer a unit test, but an integration test. Each call now has to travel to the DB and back, and just makes everything slower. Secondly, there are some cases in which it picks up a service which you just can’t work with in a test. Something which either needs the whole production setup, or just doesn’t work in a unit test. And since it reaches into static factories to get that, there’s no way for you to slip in your mock.
So instead, start passing in what is needed to your constructor. This forms the basis of Dependency Injection, or slipping in a fake, or whatever you want to call it. Basically, your constructor takes in what it needs, and all it does is assign stuff to local variables. No work is done there. So the above code becomes something like :
XPathConvertor(XPathTranslator translator) {
this.xpathTranslator = translator;
}So much cleaner, and only has what it needs. So in our test, you can create a translator and pass it in which uses a mock db, or pass in a fake translator or whatever. The point is, testing becomes easier.
- Global State :
The second biggest complaint with untestable code. It usually has to do with Global state. Or as some people like to call it, putting things in and pulling things out of ether. This might be anything from using global static singletons to static method calls inbetween your method.How is this bad ? Consider some method you are testing. What if it suddenly reaches out into the ether, and grabs some object and uses it to perform its calculations. You say, ok, I can somehow add a setter which allows me to set its state. Now what if there are multiple tests running in parallel…. Yes, exactly. Not good. Furthermore, you can’t mock a static method, which makes life miserable.
Consider this question then, why does it need to be static ? What benefit are you getting, other than the fact that you don’t need to create an object ? Is it really worth making code untestable in order to reduce one line of code of creating an object ? The answer 9 times out of 10, I find, is no.