ashleyfrieze posted: " When I produced System Stubs I attempted, and failed, to produce a mock implementation for the system clock. It's very convenient in production code to create a variable containing something like DateTime.now() and then perform time calculations b"
When I produced System Stubs I attempted, and failed, to produce a mock implementation for the system clock.
It's very convenient in production code to create a variable containing something like DateTime.now() and then perform time calculations between that instant and some other date.
How Do We Mock It For Testing?
I'm going to attempt to surprise you by telling you that, in my experience, most attempts to mock the above in Java, end in failure.
In other languages, it can be possible to inject some alternative behaviour into the global get function for the current time. When that's possible, then it's a great solution.
At some point, I may figure out how to intercept the calls to System.currentTimeMillis() in Java and produce the ClockMock I so want to produce... but until then:
Stop trying to mock the current time in Java with an abstraction!
Why Can't I Mock Current Time?
You can.
But you just said...
I said don't introduce an abstraction. Introducing a known time value into an algorithm that calculates time is ok. But introducing a wide-spread time getter abstraction is not.
Consider the following:
int getDriverAge(Date dateOfBirth, TimeGetter timeGetter) { return roundToWholeYears(daysSince(dateOfBirth, timeGetter)); } int daysSince(Date date, TimeGetter timeGetter) { Date now = timeGetter.getNow(); // return days between date and now }
In the above example, the TimeGetter has spread through a couple of functions. How many other tiers above were there holding this abstraction of now() that's only there so a test can replace it with a mock?
Whether it's my made up TimeGetter or Java's built in Clock or any other number of tricks, this pattern sucks. The ability to abstract time becomes a virus in the software.
Similarly, trying to use a global singleton that's mockable creates a necessity to check that nobody's bypassing MockableTime.now() with the real now() function.
So How Can We Test Time?
Two tricks.
Use Fuzzy Assertions for time when doing grander tests - don't be bothered by the exact time output if it's not necessary/predictable in real life.
Definitely test any algorithm that depends on real dates by ensuring that its public interface allows all date inputs (including now) to be passed in.
This means that the code that's most low-level-implementation-ey should not itself call now() but should just process time data.
Slightly higher up, we know that code can safely pass now() down to lower level code, and we don't need to worry about predicting the answer.
Our tests at a higher level can use fuzzy assertions that either check is this a time stamp or is this a time stamp within a certain range - so long as that doesn't lead too much to Trial By Competitive Calculation.
TL;DR
Creating an abstraction for something as global and simple as time can create a virus throughout the entire codebase, where loads of modules suddenly need to know about something that's really only a hook for testing.
Choosing your battles carefully can lead to low-level code not fetching the time itself, making it easier to test, and higher level code's tests not worrying too much about precise times in results, because they're already covered by lower level tests.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.