Any test favoring developer will tell
you singletons are the bane of testability. Static contexts make
testing difficult. When dealing with legacy code, we are often faced
with the problem of testing around such problematic coupling. I've
had enough opportunity to dable in such code to have developed some
strategies in dealing with coupling. In this post I'll present a
strategy I refer to as abstracting out singletons.
For the purpose of discussion I present
the following code fragment:
public class TightlyCoupled {
public void quickPurchase(String customerReferenceNumber,
int productCode, int quantity) {
CustomerCatalog customers = CustomerCatalog.getInstance();
Customer purchaser = customers.find(customerReferenceNumber);
if (purchaser == null)
throw new UnknownCustomerException(customerReferenceNumber);
OrderItem requestedItems = Inventory.getInstance().take(productCode, quantity);
Order quickOrder = new Order(purchaser);
quickOrder.add(requestedItems);
// ...
}
}
This code is displays the symptoms of
coupling that clearly make testing difficult. Can the inventory and
customer catalog be easly programmed to behave in a predictable and
testable manner? Your answer will be contextual, but often they
cannot. In most enterprise application such singletons will bootstrap
the initialization of other singletons as well as database
connections. Can these services be easily mocked? Doubtful.
I have seen cases where programmers
will allow themselves to override the singleton before the test
scenario. This works, but does not help to move away from the current
programming model.
What we want is to be able to provide
an alternate implementation of these services to a constructed
instance of the class under test (CUT). The problem with using a
dependency injection approach to testing is that most applications
developed with singleton spread are rarely blessed with an IoC
container to facilitate object construction. Consequently we can
rarely allow ourselves to remove the default constructor of the CUT.
So the first thing that I propose is
that we consider using a DI approach, and in order to support this we
provide two constructors for our CUT: one that allows for the
injection of the dependencies, an a default constructor that wires
the class with the default dependencies. This quickly leads us to
something like this:
public class LessCoupledThroughConstructor {
private CustomerCatalog customers;
private Inventory inventory;
public LessCoupledThroughConstructor(
CustomerCatalog customers,
Inventory inventory) {
this.customers = customers;
this.inventory = inventory;
}
public LessCoupledThroughConstructor() {
this(CustomerCatalog.getInstance(), Inventory.getInstance());
}
public void quickPurchase(
String customerReferenceNumber,
int productCode, int quantity) {
Customer purchaser = customers.find(customerReferenceNumber);
if (purchaser == null)
throw new UnknownCustomerException(customerReferenceNumber);
OrderItem requestedItems = inventory.take(productCode, quantity);
Order quickOrder = new Order(purchaser);
quickOrder.add(requestedItems);
// ...
}
}
So now our CUT can be passed a test
friendly set of dependencies with a minimal impact on existing code.
What's more, if the CUT is normally treated as a singleton also, its
default constructor can become private, and its instance accessor
would not take any dependencies. The test friendly constructor would
remain public.
This approach has one drawback. When
working on a legacy system that relied heavily on singleton accessors
to initialize its services, I quickly discovered that many of these
services had developed a kind of magical initialization order. No one
knew for certain what that order was, but many bugs in the system
were to it. This minor adjustment I proposed broke the
initialization order: the dependency services where initialized in
the constructor, and not on first use in the using method. Ka-boom!
No problem. The solution is to
introduce a minimal amount of abstraction. I decided to creation some
wrapper services. These services delegated operations to the static
context on a per-call basis, but where not singletons or static instances themselves. This produces the code bellow:
public class LessCoupledThroughWraping {
private CustomerCatalog customers;
private Inventory inventory;
public LessCoupledThroughWraping(
CustomerCatalogService customers,
InventoryService inventory) {
this.customers = customers;
this.inventory = inventory;
}
public LessCoupledThroughWraping() {
this(new GlobalCustomerCatalogService(), new GlobalInventoryService());
}
public void quickPurchase(
String customerReferenceNumber,
int productCode, int quantity) {
// as previous example
}
}
To give you an idea of what the wraper
services look like:
public class GlobalInventoryService implements InventoryService {
public OrderItem take(int productCode, int quantity) {
return Inventory.getInstance().take(productCode, quantity);
}
}
This approached allows the CUT to define a default set of dependencies, but
still provides the possibility to inject dependencies.
These
approaches are clearly not good examples of applied TDD, but on large
systems that have a great deal of code coupling it is often necessary
to introduce such test seams. Gradually a large system becomes split
into an IoC container friendly design with well tested components.
Other
areas where such a strategies work include all static data, static
third party APIs, as well as static platform functinality.
An
example that I used recently involves the .NET runtime's
“DateTime.Now” which can make testing time dependent behavior
unpredictable. My goal was to test that after 10 minutes some condition expired. Obviously I didn't want a test that ran for ten minutes. I abstract out a date and time service and at test
time I substitute a date time service on which I can set the desired
date and time. This makes testing cleaner and more reliable, and in this last case I only needed to redefine the date/time service's time to jump ahead ten minutes.
What
do you think? Have you encountered similar problems? What did you do
to work around them?