|
Calendar.getInstance(), although seemingly
innocuous,
links the code to the clock of the machine on which it is
executing, and even though software developers can tend to be
messianic on occasions, few of us have demonstrated any control over
the eternal march of time. This presents us with a problem
testing
this codebase: we cannot write a deterministic test of the Christmas
shipping charges logic because we cannot control the time returned to
us by the environment. Actually, if we wanted to we could adjust
the system clock before running the test and then reset it afterwards,
but that's a sub-optimal solution compared to the one we'll arrive
at towards the end of this article.Calender to outside this class. The
easiest way
to achieve this at the moment is to add a new Calendar
attribute to the
ProductShipper class. |
Calendar is
no longer held
by ProductShipper, all code that uses this class must
supply a Calendar
to the ProductShipper before use. At the moment, ProductShipper
has no mechanism for external classes to inject the dependency, so
we'll use a setter method to allow this access. ...
|
calculateShippingCharges()
method. I've chosen to use a setter for no particular reason.ProductShipper, we need to extract a Java interface
out of the
concrete implementation to provide us the ability to mock or stub the
implementation at the appropriate moments. Because there are two
method calls being made against the Calendar attribute
(one to retrieve
the current month, one to retrieve the current day), we'll add two
similar (but slightly more streamlined) methods to the new
interface. This leaves us with our ProductShipperCalendar
interface shown
below. public interface ProductShipperCalendar { |
ProductShipperCalendar interface in hand,
we can now
change all instances of Calendar in our ProductShipper
to
ProductShipperCalendar. This will have the flow-in
effect of
needing to change the existing Calendar method calls to
match those defined
in our ProductShipperCalendar interface. I've
underlined the relevant changes in ProductShipper below. |
ProductShipper is using the ProductShipperCalendar
interface,
we can implement this interface to provide a configurable stub version
where we can
specify exactly which month and day will be returned. As you can
see below, I've done this simply by providing a couple of attributes
for the day and month with accompanying getter and setter methods. |
Calendar itself. |
ProductShipper
front, we can
write a fairly simple JUnit test to exercise the code path for
calculating Christmas shipping charges. I've shown just one of
these tests below - in reality, you would also need to test the various
edge conditions around this logic. public void testCalculateChargesIncludesAdditionalChargesDuringChristmas() { |
ProductShipper
class that was preventing us testing the code using this dependency to
calculate excess shipping charges. Using the StubCalendar,
we can
now simulate running the ProductShipper code at any time
during the
year should more complex time-sensitive charging policies be added at a
later date. Some important points to note when writing the tests
for ProductShipper:RealCalendar
implementation for all non-Christmas tests. This will result in
tests which will run propertly at all times except the Christmas period!
If you truly have no control over your dependency, use a stub/mock
implementation for all unit testing. Leave testing using the real
implementation to an integration test and make sure you don't assert
any values which are affected by the dependency.ProductShipperCalendar
might
have only included the duringChristmasPeriod() method,
moving this
calculation out of ProductShipper completely. For
simplicity, I
would recommend this approach only when other time-based charges are
being introduced.