Rule of Thumb: Ask for the Clock

posted by Craig Gidney on April 16, 2013

There are a lot of ways to make your code hard to test.

The classic example is touching global mutable state, which destroys the independence between tests and introduces lots of setup and teardown concerns. Of course, global mutable state has so many other downsides that avoiding it is a good idea even if you’re not going to write any tests at all.

Another example of what not to do, that doesn’t matter as much on metrics other than testability, is failing to ask for external dependencies. Code is a lot harder to test when it directly accesses external systems like the file system, networks, sensors, or the subject of this week’s trying-to-be-concrete post: time.

Time Twists Testing

Code that depends on the time, or date, is hard to test.

Suppose you want to test a long timeout: a prompt should appear when a user is inactive for five minutes. A test that waits for five minutes, and fails if the programmer moves the mouse during that time, is clearly unacceptable.

Alternatively, suppose you want to test that an event that should occur “once per day at 12:30am” doesn’t occur twice on daylight savings days where time “falls back” and repeats 12:30 am. No, having the tests pass once per year is not okay. Neither is changing the system time (who knows what sorts of things other programs will do when the apparent time jumps by years).

Testing edge cases based on time is extremely important, but you can’t afford to actually wait to do the testing. For example, I bet Microsoft’s not waiting four years to find out if the mistake that took down Azure, computing the expiration date of certificates as Feb 29, 2012+1 = Feb 29, 2013 [not a valid day], is fixed.

Clearly we want to do something to cause code to act as if it’s a particular day, or as if a large amount of time has passed, without actually waiting for those things to happen in real time. But what?

Mediocre Solution: Mocking

A mocking framework allows you to (among other things) intercept invocations of methods like “get the current date”, and have methods under your control be invoked instead. This allows you to do things like make code see a particular date.

Mocking is very flexible, but it requires specific knowledge of the code’s internals. You need to know how the code implements its functionality in order to know what to mock. It won’t do much good to mock DateTime.Now if the code uses DateTime.UtcNow or Environment.TickCount or System.Diagnostics.StopWatch or a custom NTP client or invokes external time-dependent code or … uh, you get the idea. Mocking can easily drift from testing the surface of the code (that a particular goal is achieved), into testing the code’s internal minutia (that particular methods are used to achieve a goal).

I don’t recommend relying on mocks solving your time-testing problems. If your mocking framework can mock the flow of time in a unified way, so that all the various methods to access the time give consistent results, then that should work fine for testing. If it can’t… bleh. You should be solving designing code so these problems don’t occur, not solving them one by one with assumptions that break every time the code changes.

My Preferred Solution: Ask for the Clock

The method I use to create testable time-dependent code is to ask for a “clock”. A clock is a provider for the progression of time, an instance of an interface with methods for all the various time-related things the code might want to do: delaying an action, measuring elapsed time, creating periodic events, querying the date, whatever you need.

When the code is being run in production, the provided clock will be advancing with respect to external time. When the code is being tested, the provided clock will be a “fake” manually controlled instanced that the test can advance as needed.

Here’s an example of a simple method that takes a clock, in order to make itself trivially testable:

TimeSpan MeasureElapsedTime(Action action, IClock clock) {
    var t = clock.Time();
    action();
    return clock.Time() - t;
}
void MeasureElapsedTimeTest {
    var c = new ManualClock();
    var d = TimeSpan.FromSeconds(5);
    Assert.IsTrue(MeasureElapsedTime(() => c.AdvanceBy(d), c) == d);
}

The cost you pay for asking for clocks is ‘constructor bloat’. It’s no good to have a global clock or a singleton clock, because different tests use different clocks and you don’t want them to interfere with each other. Each class that wants a clock ends up asking for it in its constructor (or in the relevant methods). You end up handing the clock around a lot (along with sorts of external provider interfaces). The issue can be alleviated somewhat by dependency injection frameworks, which basically do the propagation for you.

Bonus Benefits

Interestingly, once you’re asking for clocks everywhere, you can start reaping some benefits unrelated to testing.

For example, consider that program that shows a prompt after five minutes of inactivity. Suppose a user starts the program, closes their laptop to put it to sleep, comes back an hour later, and opens their laptop. What happens? The program throws up an “Are you still there?” prompt in response to user activity. I had an old laptop that did this with the screensaver: I’d open the laptop, see the desktop for a second, and then the screen saver would trigger because I hadn’t moved the mouse in “hours”.

In the above example, the problem is that the program is waiting for external time to pass when it should actually care about program-is-active time. When the computer hibernates, or a debugger breakpoint is hit, the program is unable to be active and so the timeout should pause. Given that you’re already passing clock around, why not use a clock that pauses when the program pauses and save yourself some trouble when debugging?

I actually implemented exactly such a clock, and use it regularly. Below is a ‘ProgramClock’, which wraps a ‘PauseSkippingClock’ (pretends no time passed when its underlying clock has advanced a lot since the last call), which itself wraps a ‘SystemClock’ that advances with external time. When the program is halted for more than a few seconds a periodic callback can’t occur, the PauseSkippingClock detects a pause (not shown), and the halted time is skipped.

///<summary>
/// An <see cref="IClock" /> that advances in real time while the program is running.
/// Doesn't advance while execution is halted.
/// For example, stops during debugger breakpoints and during system hibernation.
/// </summary>
/// <remarks>
/// Uses a shared <see cref="PauseSkippingClock" /> to ensure only one periodic check occurs.
/// (The shared instance is garbage collectable when no <see cref="ProgramClock" /> instances exist.)
/// </remarks>
[DebuggerDisplay("{ToString()}")]
public sealed class ProgramClock : IClock {
    private static readonly TimeSpan TickPeriod = 1.Seconds();
    private static readonly TimeSpan PausePeriod = 3.Seconds();
    private static readonly WeakReference<IClock> BackingClock = new WeakReference<IClock>(null);
    private static readonly object BackingClockLock = new object();
    private readonly IClock _clock;

    public ProgramClock() {
        lock (BackingClockLock) {
            if (BackingClock.TryGetTarget(out _clock)) return;
            this._clock = new PauseSkippingClock(new SystemClock(), TickPeriod, PausePeriod);
            BackingClock.SetTarget(this._clock);
        }
    }

    public Task At(Moment time, CancellationToken ct = default(CancellationToken)) {
        return _clock.At(new Moment(_clock, time.Ticks), ct);
    }

    public Moment Time() {
        return new Moment(this, _clock.Time().Ticks);
    }

    public override string ToString() {
        return String.Format("ProgramClock: {0}", _clock);
    }
}

Note that the code uses a “dumb” periodic callback to detect the program being paused, because the amount of work that would be required to (incorrectly) figure out all the ways a program might be paused as well as how to “properly” detect each happening is absurd.

Ironically, due to the need to ensure allocating many program clocks couldn’t cause many periodic callbacks and due to the extraction of testable bits into PauseSkippingClock, ProgramClock is not very testable. I’m not above making tradeoffs.

Other clocks I’ve implemented and used include PausableClock and MultiplicativeClock. They come in handy when you want things to stop or run at a different speed, without having to rewrite those components. It is so incredibly satisfying to make a simple game pausable by changing two lines of code.

Summary

Asking for a clock makes time-dependent code easier to test, and adds some unexpected flexibility as a bonus. It is a specific instance of “Ask For What You Need”, a basic tenet of writing clean testable code.

You can pull the same trick with other external dependencies. Sometimes the appropriate type is even already in the language. For example a class that asks for an IP address in order to create a NetworkSocket in order to get a (network) stream is hard to test. A class that just asks for a stream is easy to test, and more flexible.

Discuss on Reddit, Hacker News


Twisted Oak Studios offers consulting and development on high-tech interactive projects. Check out our portfolio, or Give us a shout if you have anything you think some really rad engineers should help you with.

Archive

More interesting posts (13 of 21 articles)

Or check out our Portfolio.