Tuesday, May 25, 2010

Temporal testable code

A lot of code is time dependent. Code either needs to wait or timeout for a specific amount of time, or it needs to fetch the current system time for some reason.

If the code uses Thread.sleep(), System.currentTimeMillis(), new Date(), or anything like that, it has a hidden and implicit dependency. This can be a problem when you're trying to write tests for such code.

The tests usually end up with a lot of:

Thread.sleep(1000); // wait for code to finish doing its job.

This will make your test suite unnecessarily slow to run, and you want your tests to finish almost instantly.

The solution is to convert this implicit dependency to an explicit!
This is done by creating the following interface:

public interface TimeProvider {
long now(); // Replaces System.currentTimeMillis();
sleep(long timeInMilliSeconds) throws InterruptedException; // Replaces Thread.sleep
}


All the code that is time dependent in any way should inject an object of this interface.
Now, you need two implementations of this interface, one for real usage and one for testing. Fortunately, these are trivial to write:


public class SystemTimeProvider implements TimeProvider {
@Override
public long now() {
return System.currentTimeMillis();
}

@Override
public void sleep(long timeInMilliSeconds) throws InterruptedException {
Thread.sleep(timeInMilliSeconds);
}
}

public class MockTimeProvider implements TimeProvider {

private long now;

public void setNow(long now) {
this.now = now;
}

public void advanceTime(long amountInMillis) {
now += amountInMillis;
}

@Override
public long now() {
return now;
}

@Override
public void sleep(long timeInMilliSeconds) {
now += timeInMilliSeconds;
}
}


For the real application, everything will behave the same.
For testing purposes, just inject the MockTimeProvider instead, and manually manipulate the time.

Here's simple example to illustrate how this works in practice.
Basically we want to ensure that when we generate UUIDs, a specific part of the UUID remains the same if two UUIDs are generated close enough temporally, but different if there's a small time difference:


public class UUIDTest {
private final MockTimeProvider mockTimeProvider = new MockTimeProvider();
private final UUIDFactory factory = new UUIDFactoryImpl(mockTimeProvider, new SecureRandom());

@Test
public void combTest() throws InterruptedException {

mockTimeProvider.setNow(0);
UUID firstUuid = factory.combUUID();
UUID secondUuid = factory.combUUID();

assertFalse(firstUuid.equals(secondUuid));
long combPart1 = getCombPart(firstUuid);
long combPart2 = getCombPart(secondUuid);
assertEquals(combPart1, combPart2);

mockTimeProvider.advanceTime(20);

UUID thirdUuid = factory.combUUID();
long combPart3 = getCombPart(thirdUuid);
assertFalse(combPart1 == combPart3);

}

private long getCombPart(UUID uuid) {
String combString = "0x" + uuid.toString().substring(24, 36);
return Long.decode(combString);
}
}
Stop using static ways of using time in your code!
Using an injected TimeProvider works almost identically if it's a SystemTimeProvider, so the only cost is a slightly more costly method invocation since it's through an additional abstraction layer.
But the benefits are huge! Your code will be more testable, and the dependency on time is no longer hidden.

No comments:

Post a Comment