Testability != Good Design

Standard

It’s a funny thing, testability. It’s not really defined, or rather, it is defined poorly.

If testable code is code we can test, that means all code is like that. We can test it through unit tests. If it’s hard we can move to functional tests. And if all fails, we can do manual testing. Even performance testing exercises the code. There might be code that tests cannot exercise, but then why did we write it in the first place?

When we talk about testability we usually mean “hard to test”. That is a whole discussion by itself, because “hard to test” is also subjective. If we follow the theme of testing as an investment to minimize future maintenance costs, then “hard to test” translates to  “Costly to test” or “risky to test”.

So here’s another fun fact: When we have a well-factored code, it requires minimal changes, if any, for it to be tested. There’s no surprise that focusing on SRP makes code testable.

Because well-designed code is testable, we tend to correlate the two. Hard to test code is usually not factored well, and the two seem to go together. Moreover, we may infer that testable code leads to good design. It can in many cases, but not always.

Here’s an example. We have a Customer class with a static method that gets the balance of an Account from a Bank:


This is a very straightforward, readable method. Its use of a static method (getAccount) makes it “untestable” in Java and other languages. Again, by “untestable” we really mean “hard to test”, which then translates to “hard to mock”. In our case, using regular methods, it will be hard to mock the static method and control the input.

If we rule out use of PowerMockito, we need to modify our code to make it ”testable”. We can refactor it to pass the Account as a parameter. Once the Account is passed as an argument (really as an interface), we can mock the IAccount interface and pass it in. We now have testable code.


But has the design improved?

The method is readable as before. We exposed the type of account, although I’m not sure we even needed to know about it. We no longer have the name parameter, but instead, we needed the caller to extract the account before the method call, while originally it did not need to bother with the Bank at all.

The design has changed, but maybe not for the better. It definitely complicated the calling code.

Now let’s try another design change for the sake of testability. This time instead of extracting a parameter, we’ll inject it with a dependency injection container (I’ll use Geuce). For that we need to modify the Customer class, and add:

Has its design improved now?

For the Customer class, we’ve added an unnecessary public setter, and a field we didn’t need before. If the calling code just used the setter, we’ll be in a similar condition to the last example, but using a DI framework makes the calling code, including wiring and configuration again, more complicated. (By the way, in .net it looks a bit better, but not by much.)

You may argue that sacrificing the simplicity of the calling code in order to make the design of the tested object is ok. But if you’re going to test the calling code, you’ll need to use the same tricks, and if you’re not, well, you just made it more complex and susceptible to bugs. You should test it.

Testable code is not inherently designed better

Sometimes the changes are risky and costly. We need to balance the need for testing with the risk, and how the tools we use impact the design.

And let’s remember: this code was not “untestable”. We set a constraint to not use PowerMockito, and tried to work around it. We could easily tested that code as-is.

For years I’ve heard that tools like PowerMockito and Typemock Isolator, encourage bad design, because they allow to test badly designed code. It sounds bad, but it maybe a better solution than making risky changes so you can just test. Sometimes the changes are not even risky, but will create a more complex code, where it should be simple.

Testing and design are broad skills every developer should have.

As long as you’re making a knowledgeable decision, not based on popular slogans, you’ll be fine.

Image source: https://www.flickr.com/photos/microassist/7268711202/

5 thoughts on “Testability != Good Design

  1. Anonymous

    It is evident, that the isOverDrawn is not the property of the Customer, but the Account.
    What if the Customer has several accounts???

    So the more testable code pointed out some discrepancy in your design, at least…

  2. Ah, you introduce domain knowledge into the story. Well played.

    Note, it is not evident, but assumed 🙂 I agree that well designed code explains the domain. I have failed in this example, because of my assumptions…

  3. Anonymous

    The “evidency” comes from the line “getAccount().getBalance()>limit”, which seems to break the simple responsibility principle, so it is not a domain knowledge.

  4. It also breaks the Law of Demeter. It’s not a SOLID code, and it still deserves to be tested.

    I’ll try to make my examples clearer in the future. My intention was to explain the costs of changing the code to testability for the overall design.

    Good design is in the eye of the beholder. SOLID is thought to be a good bar by many. But not always and not by everyone. Singletons may be evil to some, but are a good solution to others.

    I think I’ll write on this in a separate post.

    Thanks for the feedback,
    Gil

  5. Anonymous

    Define good design first.

    If you define it as SOLID, then easily testable code is well designed.

    If you define it as code which costs the least for initial development and subsequent maintenance, then easily testable code is well designed.

    Code being easily testable may not be the only characteristic of good design, but it’s an extremely important one.

Leave a Reply

Your email address will not be published. Required fields are marked *