Guidelines for Testable Design
Testability issues
A programmer struggling to write a test is facing off against one of a few common show-stoppers. They mostly fall under two categories: restricted access, and inability to substitute certain parts of the implementation.
Can’t instantiate a class
This mostly happens with third-party libraries that weren’t designed with testability in mind. Another common cause for the inability to instantiate a class is that constructor depends on some static initialization.
Can’t substitute a collaborator
If the method is supposed to engage in an interaction with collaborating objects, but you find yourself incapable of intercepting the interaction you’re interested in. This might be because the collaborator is hard-wired into the method/class you’re testing and can’t be substituted with a test double.
Avoid complex private methods
You should strive to write private method so that you don’t feel
the need to test your private methods directly, make your public
methods read well, it should be perfectly fine that they get
tested
only through those public methods.
Avoid final methods
The main purpose of marking a method as final is to ensure that it isn’t overwritten by a subclass, but often burden of worse testability outweigh the benefit of having the final.
Avoid static methods
A static method is easy to write, but you’ll have a hard time later if you ever need to stub it out in a test. Instead, create an object that provides that functionality through an instance method.
Use new with care
Every time you “new up” an object you’re nailing down its exact implementation. For that reason, your methods should only instantiate objects you won’t want to substitute with test doubles, it should be passed into the method somehow rather than instantiated from within that method.
Avoid logic in constructors
Constructors are hard to bypass because a subclass’s constructor will always trigger at least one of the superclass constructors. Hence, you should avoid any test-critical code or logic in your constructors.
Avoid the Singleton
There are situations where you want just one instance of a class to exist at runtime. But the Singleton pattern tends to prevent tests from creating different variants. If you do find yourself with a static Singleton accessor, consider making the singleton getInstance() method return an interface instead of the concrete class. An interface is much easier to fake. The much testable design would be that doesn’t enforce a single instance but rather relies on the programmers’ agreement or modern dependency injection framework.
Favor composition over inheritance
Inheritance does allow you to reuse code but it also brings a rigid class hierarchy that inhibits testability. If your intent is to reuse functionality it’s often better to do that by means of composition: using another object rather than inheriting from its class.
Wrap external libraries
Wrap the untestable piece of code of the external library behind your own interface, a thin layer that is testable that’s test-friendly and makes it easy to substitute the actual implementation.