Guidelines for Java Testable Design

Issues with testability in Java boil down to our inability to write tests or the excess trouble we have to go through to get it done. In this article, based on chapter 7 of the book “Effective Unit Testing – A guide for Java developers”, Lasse Koskela shares a set of dos and don’ts for testable design. In the tips provided, he recommends to avoid complex private methods, static methods, logic in constructors and to favor composition over inheritance.

Author: Lasse Koskela, http://lassekoskela.com/

This article is based on Effective Unit Testing – A guide for Java developers, published in February 2013. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. Visit the book’s page for more information.

Guidelines for Java Testable Design - How to Do It Right

Issues with testability boil down to our inability to write tests or the excess trouble we have to go through to get it done. These troubles include:

  •   Inability to instantiate a class.
  •   Inability to invoke a method.
  •   Inability to observe a method’s outcome or side-effects.
  •   Inability to substitute a test double.
  •   Inability to override a method.

Having faced off with these testability issues again and again over the years, we have come to appreciate a few simple heuristics — guidelines — for testable design. Let’s take a look at them. The following is a set of dos and don’ts I have gathered and whose importance I’ve learned through repeated yak-shaving. They aren’t in any particular order and none of them are universal truths—just something to keep in mind so that you think twice before deciding to go against these guidelines.

Avoid complex private methods

There’s no easy way to test a private method. Therefore, we should strive to make it so that we don’t feel the need to test our private methods directly. Note that I didn’t say you shouldn’t test your private methods but that you shouldn’t test them directly. As long as those methods are trivial utilities and shorthands to make your public methods read well, it should be perfectly fine that they get tested only through those public methods.

When the private method isn’t all that straightforward and when we do feel like we want to write tests for it, we should refactor our code so that the logic encapsulated by the private method gets moved over to another object responsible for that logic—and where it is a public method as it should be.

Avoid final methods

Very few programs need final methods. The odds are that you don’t need them either.

The main purpose of marking a method as final is to ensure that it isn’t overwritten by a subclass. In fact, Oracle’s Secure Coding Guidelines for the Java Programming Language suggests that “making a class final prevents a malicious subclass from adding finalizers, cloning and overriding random methods.” While that is true in some context, it doesn’t mean that you should need the final modifier.

There are two problems with the above logic. First, those potential subclasses are likely written by the people sitting next to you. Second, the Reflection API can be used to remove the final modifier. In practice, the only situation where you might reasonably want to make a method final is when 1) you load foreign classes at runtime or 2) you don’t trust your colleagues (which sounds like you have much bigger issues to worry about).

What about the performance of final?

One of the arguments that people sometimes lean on in support of final methods is performance. Namely, it is said that since final methods cannot be overridden, the compiler can optimize the code by inlining the method’s implementation.
It turns out that the compiler cannot safely do this because the method might have a non-final declaration at runtime. However, a JIT compiler would be able to inline such methods at runtime, which does present a theoretical performance benefit.

With that said, Jack Shirazi and Kirk Pepperdine, the authors of Java Performance Tuning, Second Edition, and javaperformancetuning.com, have said, “I wouldn’t go out of my way to declare a method or class final purely for performance reasons. Only after you’ve definitely identified a performance problem is this even worth considering.”

Avoid static methods

Most static methods shouldn’t be. The reason to have them is generally either because “they don’t relate to a specific instance of a class” or because “we couldn’t bother figuring out where it belongs so we just made it a static method in this utility class.” The former motive is a solid one but the latter is mere ignorance or lack of interest.

There are indeed a group of methods that are naturally static. For example, a utility method that performs a calculation on given numbers would likely be a good candidate for a static method. So what distinguishes a method as one that should or shouldn’t be static? Maybe the example in listing 1 clarifies the idea.

Listing 1 Avoid static methods that you might want to stub out
public static int rollDie(int sides) {
return 1 + (int)(Math.random() * sides);
}

The rollDie method in listing 1 produces a random result as if we’d roll a die with a given number of sides. Dealing with random factors in automated tests is generally something we’d rather avoid so we will very likely want to stub out the rollDie method in our tests.

My rule of thumb is to not make it static if you foresee that you might want to stub it out in a unit test one day. It turns out that I rarely need to stub out a pure calculation. On the other hand, I frequently find myself wanting to stub out static methods that serve as an entry point to a service or a collaborator object.

Pay attention to the kind of methods you declare static. It’s 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

The new keyword is the most common form of hardcoding. Every time we “new up” an object, we’re nailing down its exact implementation. For that reason, our methods should only instantiate objects we won’t want to substitute with test doubles. Listing 2 points out this pattern.

Listing 2 The “new” keyword hardcodes the implementation class
public String createTagName(String topic) {
Timestamper c = new Timestamper();
return topic + c.timestamp();
}

The method in listing 2 builds a string based on the given input and a timestamp generated by a Timestamper, which is instantiated inside the method.

Many programmers don’t think of new as the kind of red flag that static and final are for test-infected programmers. That doesn’t make it completely innocent, however, because there is no way (other than byte code manipulation) for a test to override the specific class instantiated within the method under test—for example, the Timestamper in listing 2.

When instantiating objects, we should ask ourselves if this object is something we would want to swap out in a test. If it is a true collaborator and we might want to change its implementation on a test-by-test basis, it should be passed into the method somehow rather than instantiated from within that method.

Avoid logic in constructors

Constructors are difficult to bypass because a subclass’ constructor will always trigger at least one of the superclass constructors. Hence, we should avoid any test-critical code or logic in our constructors.

Let’s take listing 3 as an example. It’s a contrived implementation of a universally unique identifier (UUID version 1), which is made up of the generating computer’s MAC address and a timestamp.

Listing 3 Doing too much in a constructor
public class UUID {
private String value;
public UUID() {
// First, obtain the computer’s MAC address by
// running ipconfig.exe and parsing its output
long macAddress = 0;
Process p = Runtime.getRuntime().exec(
new String[] { “ipconfig”, “/all” }, null);
BufferedReader in = new BufferedReader(
new InputStreamReader(p.getInputStream()));
String line = null;
while (macAddress == null &&
(line = in.readLine()) != null) {
macAddress = extractMACAddressFrom(line);
}
// Obtain the UTC time and rearrange
// its bytes for a version 1 UUID
long timeMillis = (System.currentTimeMillis() * 10000)
+ 0x01B21DD213814000L;
long time = timeMillis << 32; time |= (timeMillis & 0xFFFF00000000L) >> 16;
time |= 0x1000 | ((timeMillis >> 48) & 0x0FFF);
...
}
}

Let’s say we’d like to test the UUID class shown in listing 3. Looking at its bloated constructor, we’ve got a testability issue: we can only instantiate this class (or its subclasses) on a Windows computer because it tries to execute ipconfig /all. I happen to be running a Mac, so I’m screwed!

A much better approach would simply be to extract all of that code into protected methods that can be overridden by subclasses, as shown in listing 4.

Listing 4 Moved logic from constructor to protected helper methods
public class UUID {
private String value;
public UUID() {
long macAddress = acquireMacAddress();
long timestamp = acquireUuidTimestamp();
value = composeUuidStringFrom(macAddress, timestamp);
}
protected long acquireMacAddress() { ... }
protected long acquireUuidTimestamp() { ... }
private static String composeUuidStringFrom(
long macAddress, long timestamp) { ... }
}

With the modifications in listing 4, we can actually instantiate UUID objects in our tests regardless of which operating system we’re running by overriding the specific bits we want:
@Test
public void test() {
UUID uuid = new UUID() {
@Override
protected long acquireMacAddress() {
return 0; // bypass actual MAC address resolution
}
};
...
}

To summarize this guideline, whatever code we find in our constructors let’s make sure that it’s not something we’ll want to substitute in a unit test. If there is such code we better move it to a method (or a parameter object) we can override.

Avoid the Singleton

The Singleton pattern has cost this industry millions and millions of dollars in bad code. It was once written down as a pattern for “ensuring a class has one instance, and to provide a global point of access to it.” I suggest you don’t need either half of that proposition.

There are situations where you want just one instance of a class to exist at runtime. However, the Singleton pattern tends to prevent tests from creating different variants, too. Let’s take a look at the traditional way to implement a Singleton, shown in listing 5.

Listing 5 Traditional implementation of the Singleton pattern
public class Clock {
private static final Clock singletonInstance = new Clock();
// private constructor prevents instantiation from other classes
private Clock() { }
public static Clock getInstance() {
return singletonInstance;
}
}

Making the constructor and restricting private access to the getInstance() method essentially means that we can’t substitute the Clock instance once it’s been initialized. This is a big issue because, whenever we want to test code that uses the Clock Singleton, we’re stuck:
public class Log {
public void log(String message) {
String prefix = “[“ + Clock.getInstance().timestamp() + “] “;
logFile.write(prefix + message);
}
}

Wherever the code we want to test acquires the Clock instance through the static Singleton accessor, our only option is to go and inject a new value for the Singleton instance in the Singleton class using Reflection (and we don’t want to use Reflection in our tests) or by adding a setter method for doing that.

The much better – and testable – design would be a lowercase singleton, meaning that we don’t enforce a single instance but rather state out loud, “we will only create one (in production).” After all, if we need to protect ourselves from sloppy team mates we’ve got bigger problems to worry about.

Favor composition over inheritance

Inheritance for the purpose of reuse is like curing a sore thumb with a butcher knife. Inheritance does allow us to reuse code but it also brings us a rigid class hierarchy that inhibits testability.

I’ll let Miško Hevery, a clean code evangelist at Google, explain the crux of the issue:
The point of inheritance is to take advantage of polymorphic behavior NOT to reuse code, and people miss that; they see inheritance as a cheap way to add behavior to a class. When I design code, I like to think about options. When I inherit, I reduce my options. I am now subclass of that class and cannot be a subclass of something else. I have permanently fixed my construction to that of the superclass, and I am at a mercy of the superclass-changing APIs. My freedom to change is fixed at compile time.

On the other hand, composition gives me options. I don’t have to call a superclass. I can reuse different implementations (as opposed to reusing your super methods), and I can change my mind at runtime, which is why, if possible, I will always solve my problem with composition over inheritance. (But only inheritance can give you polymorphism.)

Note that Miško isn’t saying inheritance is bad—inheritance for polymorphism is totally okay. However, if your intent is to reuse functionality, it’s often better to do that by means of composition: simply using another object rather than inheriting from its class.

Wrap external libraries

Not everybody is as good at coming up with testable designs as you are. With that in mind, be extremely wary of inheriting from classes in third-party external libraries and think twice before scattering direct calls to an external library throughout your code base.

We already touched on the testability problems with inheritance. Inheriting from an external library tends to be worse, though, because you don’t have as much control over the code you’re inheriting from. Whether it’s through inheritance or direct calls, the more entangled your code is with the external library, the more likely it is that you’ll need those external classes to be test friendly.

Pay attention to the testability of the classes in the external library’s API and, if you see red flags, be sure to wrap the library behind your own interface that is test friendly and makes it easy to substitute the actual implementation.

Avoid service lookups

Most service lookups (like acquiring a Singleton instance through a static method call) are a bad tradeoff between a seemingly clean interface and testability. It’s only clean because the dependency that could’ve seemingly been explicit as a constructor parameter has been hidden within the class. It might not be technically impossible to substitute that dependency in a test but it’s bound to be that much more work to do so.

Let’s take a quick look at an example. Listing 6 presents a class responsible for making remote search requests to a web service through an APIClient.

Listing 6 Service lookups are harder to stub than constructor parameters
public class Assets {
public Collection search(String... keywords) {
APIRequest searchRequest = createSearchRequestFrom(keywords);
APICredentials credentials = Configuration.getCredentials();
APIClient api = APIClient.getInstance(credentials);
return api.search(searchRequest);
}
private APIRequest createSearchRequestFrom(String... keywords) {
// omitted for brevity
}
}

Notice how the search() method acquires the APIClient with a service lookup. This design is what I suggest makes testability worse than it could be. Listing 7 shows how we might write a test for this object.

Listing 7 Stubbing out a service lookup implies an extra step
@Test
public void searchingByKeywords() {
final String[] keywords = {“one”, “two”, “three”}
final Collection results = createListOfRandomAssets();
APIClient.setInstance(new FakeAPIClient(keywords, results));
Assets assets = new Assets();
assertEquals(results, assets.search(keywords));
}

The indirection implied by the service lookup within Assets stipulates that we go through the extra step of configuring the service lookup provider (APIClient) to return our test double when someone asks for it. While it’s just a one-liner in this test it implies several lines of code in APIClient that only exist to work around a testability issue.

Now, let’s contrast this to a situation where we’ve employed constructor injection to pass our test double directly to the APIClient. Listing 8 shows what that test would look like.

Listing 8 Constructor parameters are easier to substitute
@Test
public void searchByOneKeyword() {
final String[] keywords = {“one”, “two”, “three”}
final Collection results = createListOfRandomAssets();
Assets assets = new Assets(new FakeAPIClient(keywords, results));
assertEquals(results, assets.search(keywords));
}

Again, it’s just one line shorter but that’s a full 20% less than listing 7 and we don’t need the workaround setter method in APIClient either. Aside from our code being more compact, passing dependencies explicitly through the constructor is a natural, straightforward means to wire up our objects with their collaborators. Friends don’t let friends do static service lookups.

Summary

Learning from testability issues and drawing to our experience, we described a number of guidelines to avoid the aforementioned testability issues and for arriving at testable designs. Those guidelines teach us to avoid final methods, static methods, and complex private methods. We also learned to treat the new keyword with care because it essentially hardcodes an implementation we cannot substitute.

We should avoid significant logic in our constructors because they are hard to override. We should avoid the traditional implementation of the Singleton pattern and instead opt for the just create one approach. We know to favor composition over inheritance because it’s more flexible than the class hierarchy implied by inheritance.

We made note of the danger of inheriting from and making liberal direct use of external libraries as such libraries are out of our control and often exhibit a less testable design than our own. Finally, we learned to avoid service lookups and go for passing our dependencies in through constructor parameters instead.

While they are far from being hard and fast rules, keeping these guidelines in mind and stopping to think for a second before going against them is bound to help you devise more testable designs.

And there’s more! The Internet is chock full of good information related to testable design. My favorites are probably the Google Tech Talk videos. If you have some time to spare go to youtube.com and search for “The Clean Code Talks.” A lot of good stuff there.

1 Comment on Guidelines for Java Testable Design

2 Trackbacks & Pingbacks

  1. 2012년 2월 15일 it 기술 동향 |
  2. Software Development Linkopedia February 2012

Comments are closed.