Wednesday, December 4, 2013

The Virtual Clock Test Pattern

You can have a hard time unit-testing code that depends on the system clock. This article describes both the problem and a common, repeatable solution.

[Note: This is an article I wrote in 2006. I'll publish it here because people sometimes still quote it, and the original version fell off the Internet. (But the older version, with examples in Java instead of Ruby, can still be found on the Wayback Machine).]

 

The problem


To see what the Virtual Clock is about, we can dive into a simple example. We'll code this in Ruby, but you should be able to understand it even if you're not a rubyist – and you'll learn some Ruby along the way.

We're building a minimal scheduling system where you can create tasks and execute them later. Each task has a maximum age, after which it expires and cannot be executed any more. Here's the Task class:

class Task
  def initialize(max_age, &action)
    @max_age = max_age
    @action = action
    @time_of_birth = Time.now.to_i
  end

  def age
    Time.now.to_i - @time_of_birth
  end

  def execute
    @action.call if age <= @max_age
  end
end

The method initialize is Ruby's equivalent of a constructor – it gets called when you write Task.new to create a task. The first argument of initialize is the maximum age of the task, in seconds. The argument is stored into a variable named @max_age. The @ prefix means this is an instance variable (you might be used to call these “object fields”). Ruby being a dynamic language, you don't have to declare the variable anywhere else: you just assign something to it, and it springs into existence.

The second argument of initialize is not really an argument – it's a block. Ruby blocks might look foreign to you. They allow you to write things like:

t = Task.new(10) { print “Executing, sir!” }

The curly braced thing is the block. It contains the action that we want the task to execute. From inside the method, it looks like an argument prefixed by an ampersand. From outside, it's more like a snippet of code that's attached to the method call. It's not executed immediately. Instead, Ruby internally converts it to an object, and we store it into the @action instance variable to execute it later.

The last instance variable, @time_of_birth, is initialized with the current time. Time.now gets the time from the system clock, and the method to_i converts it to an offset in seconds from a conventional date. The method age uses the same instruction to find out how old the task is.

Finally, the method execute does the real work: it executes the stored block by invoking its call method. This is only done for tasks whose age is less than @max_age – cranky old tasks just ignore your attempts to execute them. It's idiomatic Ruby to put the if condition at the end when the body is a single statement.

This is a smart little class, but we should have written a test for it. We haven't been good Test-First coders, have we? Better late than never, so let's focus on that test. But now we have a problem: to test this code, we need to be sure that some tasks expire before we execute them, and some others don't. That's not easy. We could insert pauses into our test by calling Kernel.sleep, but that would slow down the tests. It would also make it very difficult to test edge conditions. Worse still, as the code gets more complicated, our tests might become non-deterministic. You probably know the problem: sometimes the test code runs slightly slower for any reason, and the tests fail randomly. There is nothing worse than a test that fails 10% of the times.*

Unfortunately, we cannot control system time. Or, can we?

 

A solution


We need to control time itself. We can do this by defining two separate clocks:

class RealTimeClock
  def time
    Time.now.to_i
  end
end

class VirtualClock
  attr_accessor :time

  def initialize
    @time = 0
  end
end

The RealTimeClock returns the current system time when you call its time method. The VirtualClock doesn't care about the system clock at all. Instead, it returns the value of its time attribute, which is initialized to zero and can be modified by the clock's clients (attr_accessor just tells Ruby that we want an object attribute named time, and its value is stored into an instance variable named @time).

Now we can modify the Task class to use one of our brand new clocks:

class Task
  def initialize(max_age, clock = RealTimeClock.new, &action)
    @max_age = max_age
    @clock = clock
    @action = action
    @time_of_birth = clock.time
   end

  def age
     @clock.time - @time_of_birth
  end

  def execute
    @action.call if age <= @max_age
  end
end

You can pass the clock in when you create the Task. If you ignore the argument, it will be assigned a RealTimeClock by default. Now it's easy to write a solid test:

require 'test/unit'

class SchedulerTest < Test::Unit::TestCase

  def test_active_tasks_do_something
    clock = VirtualClock.new
    executed = false
    task = Task.new(0.2, clock) { executed = true }
    clock.time += 0.2
    task.execute
    assert executed
  end
end

We created a task that uses the Virtual Clock, and instructed it to change the value of the executed flag. Then we used our own little time machine to set the current time and check that the action associated with the task is actually executed. The test for expired tasks is even simpler:

def test_expired_tasks_do_nothing
  clock = VirtualClock.new
  task = Task.new(0.2, clock) { flunk }
  clock.time += 0.21
  task.execute
end

The block associated with this task is a call to flunk, a test assertions which always fails. We're simply testing that this action is never called, and flunk is never executed. Behold the Green Bar!

 

It all boils down to...


To write good tests, we need lots of control over our test environment. We must be able to set it up exactly as we like. If a piece of code relies on non-deterministic behaviour, then we are in trouble.

The system clock is non-deterministic by nature. It's an important system property, but we cannot control it. The Virtual Clock pattern gets around this by replacing the system clock with something that we can actually control.

Therefore:

Don't access the system clock directly. Instead, wrap calls to the system clock into a Clock object, and replace it with a Virtual Clock for testing.

 

More ideas


Global clocks – If you use this pattern, you can end up passing clocks all around the place. Some people dislike this, and consider it a case of tests polluting production code. An alternate solution is a singleton clock with global access. You can make it a Real Time Clock by default, and switch to a Virtual Clock for testing. But be careful: it's safe to have a global Real Time Clock, since this is a read-only object – but the Virtual Clock isn't. I was burned by this approach when I forgot to reset the global Virtual Clock after a test, and was punished by a mysterious failure in the following test.

Not only for testing – The Virtual Clock decouples the concept of “time as an input” from “real time”. Time becomes a variable like any other. This can be useful for things other than testing. For example, you might want to simulate a process over a long time span. Or maybe you have a piece of code that processes historical data, and you want to trick it into working at a different time than “right now”.

Clock supertype - The VirtualClock and the RealTimeClock of this article don't need to share any special relationship. In dynamic languages such as Ruby and Python, it's enough that both classes implement a time method. Any piece of code that relies on time alone will gladly accept any object that implements this method (this is known as duck typing). In Java or C#, the clocks need to share the same explicit type to get this kind of polymorphic behaviour. You'd probably do this by defining a common Clock interface.

A virtual family – Instead of a Virtual Clock that just counts seconds, you might want to define a Virtual Date to abstract calendar dates. You can also adapt this pattern to deal with any non- deterministic entity, such as random number generators or external device drivers.

Related patterns – In pattern-speak, a Virtual Clock is an example of a Test Double - more specifically a Fake Object. To make objects aware of the Virtual Clock, you can use any kind of Dependency Injection. In this article, we used Constructor Injection to pass the Clock around.

 

Known uses


Martin Fowler mentioned that he always uses indirection on the system clock. He touches on the subject when he describes the Time Point pattern.

Prevalence systems such as Prevayler use a Virtual Clock to guarantee deterministic behaviour.

Real-time coders routinely simulate time. John Carmack used this technique to test its Quake 3 game engine.

There are many more examples of Virtual Clocks around. This is a common pattern. [2013 update: There are many more examples available today. The Timecop gem is one of the current popular implementations of this pattern in Ruby.]

 

Thanks to...


The following people helped me review this article, gave me comments and suggestions, or just pointed me to useful material: Kent Beck, Emmanuel Bernard, Roberto Bettazzoni, David Corbin, Chad Fowler, Martin Fowler, Patrick D. Logan, Dan Palanza, J. B. Rainsberger, Andrea Tomasini, Marco Trincardi, Andrew Wall.

* On second thought, a test that fails 5% of the times is probably worse than that.

2 comments:

  1. Hi Paolo, What you think on using JodaTime & DateTimeUtils instead of virtual clock in Java? http://stackoverflow.com/questions/20949337/is-it-safe-to-use-datetimeutils-setcurrentmillisfixed-in-tests

    ReplyDelete
  2. Hi, Vladimir. JodaTime does the thing that I called "global clock" in the post above (I should have called it "global Virtual Clock", because it's still a Virtual Clock after all). Like you implied, a global Virtual Clock is convenient on one side (you don't have to inject it, because it's global), dangerous on the other (you need to remember to restart it, or else you get test dependencies).

    I don't think there is a general guideline that works in every situation: it boils down to how much you use the Virtual Clock, personal preference, and so on. I personally tend to use injection when I'm working with Java, and a global (the TimeCop gem) when I'm using Ruby.

    ReplyDelete