Testing Time

Oooh, it’s a nuisance when you write and write automated tests for code that’s supposed to depend on the current time. Stuff’s going to go wrong.

There are essentially two categories of problem:

  • The exact timestamps output are different, so are hard to unit test
  • Simulating timings, and events happening the right distance apart, is very hard in a test if you’re using the real clock

There are some good techniques we can use. For example, we can use a fuzzy assertion to prove that the output is in the right format, and not worry too much about the exact timestamp. Similar things may also be needed when the continuous integration server has a different perspective on things like daylight savings time, and we have timestamp formats to consider.

There are also some bad or just difficult techniques. I tried quite hard, while writing System Stubs, to have a clock mock. I hit so many problems with each technique I tried, that I decided not to persevere… That said, there are legends that it’s possible to mock the actual clock. I couldn’t get traction on it.

We can create an interface between our code and the clock, making time mockable. This, generally, allows us to control time, but it makes the real code a bit clunkier, since we now have to wire the clock object in all over the place.

Enter The Dual Channel Time Method

In Java Test Gadgets, I decided to write some code that can be used to measure activity on each thread that calls through it. This allows us to monitor the number of events on each thread and, especially, the concurrency reached during a test.

What I wanted to do was capture all the events during the test, and then do some maths on the events at the end. One method was something like this:

public void threadStartedAction(Thread t) {
   // remember the start time of the thread
}

public void threadEndedAction(Thread t) {
   // remember the end time 
}

At the highest level, I wanted to run some scenarios where the actual timings against the system were quite a useful measure of success. So, 10 concurrent threads running over 100ms should all achieve an overlap and reach concurrency of 10. This needs real timings to prove itself.

However, when we got to the code which calculates the actual statistics, I wanted to be able to construct the various edge cases using plain old numbers, which meant I couldn’t contrive it with Thread.sleep called here and there in my test code.

I didn’t want to have to provide a MockClock object into my code, but there was an easy answer.

public void threadStartedAction(Thread t) {
   threadStartedAction(t, System.currentTimeMillis());
}

public void threadStartedAction(Thread t, long atTime) {
   // remember the start time of the thread
}

public void threadEndedAction(Thread t) {
   threadEndedAction(t, System.currentTimeMillis());
}

public void threadEndedAction(Thread t, long atTime) {
   // remember the end time 
}

The lower level code has two options for calling into it. We can provide no parameter for the current time, and get the actual clock, or we can call it and provide the time our code thinks it is.

This is a tiny amount of method overriding, predominantly to open the code up to its own unit tests, but it results in us being able to construct some easy to understand unit tests that can sequence time without any mocking or sleeping.

One thing to note about the tests I wrote is that for my own simplicity, I used times close to 0. This violated one of the rules of testing, which is to use realistic data. Had I not also tested with the actual clock at another tier, and had the scenarios been related to anything other than relative time, I should have tried to avoid this technique.

In a previous project, we had some real world bugs hiding on the other side of tests that used fake times, as the unit of time was not clearly agreed between different tiers of the system. This can be alleviated by using classes like LocalDateTime or Duration rather than long in cases where it might matter.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s