Just recently, I've written some code that depended on time in some way. That is always painful to test.
The problem: Testing
Let's assume some code like this:
class Timeout {
private long startMillis;
private long timeout;
public Timeout(long timeout) {
this.timeout = timeout;
this.startMillis = System.currentTimeMillis();
}
public boolean isTimeOut() {
return (System.currentTimeMillis() - startMillis) > timeout;
}
}
How would you test that? Unit tests must run fast, so you wouldn't want to call it with a timeout of of minutes or even seconds. However, time, especially in non-realtime languages like Java, is not reliable. Calling Thread.sleep(5)
in a test case may delay execution for more or less than the requested 5ms. Depending on the speed, on the number of threads running and other things, this may take much longer.
In that case, testing with a timeout of 10ms or even 100ms may create failing test cases, at least from time to time. So both waiting a long time to avoid the system inaccuracy and waiting a very short time to avoid long running unit test is not desirable. You might want to test threasholds like 1ms below or above the threshold which is not possible using real time.
Absolute vs. relative time
The next general problem with that code is using currentTimeMillis()
when we are actually not interested in getting the absolute time. That may or may not a problem in your case, but lets assume we are running that software on an embedded device that needs to sync time with a remote server. When the device starts the first time, we may have a date around 1970, but even if out time is approximately accurate, syncing the time may result in time leaps. Ths may result time dependent code that triggers immediately or won't trigger at all. Both is, depending on the application, undesirable or even dangerous.
E.g., there might be code that should trigger a system shut-down after a specified time of idling. Howver, when the system starts, gets the time sync from the server, the time leap might be larger that the idle timeout which causes an immediate shut down of the system.
Use nanoTime() instead of currentTimeMillis()
That last issue can be solved quite easily by using the method System.nanoTime()
. The return value has a resolution of nano seconds (not necessarily the accuracy) and can be easily converted to millisecond to replace currentTimeMillis()
. By contract, nanoTime
is guaranteed to have no time gaps (eventhough I've personally encountered buggy implementations) and should be favoured everywhere where no absolute time is required.
The first problem can and should be fixed by using a functional approach (which is always a good idea, btw).
class Timeout {
private long startMillis;
private long timeout;
public Timeout(long startMillis, long timeout) {
this.timeout = timeout;
this.startMillis = startMillis;
}
public boolean isTimeOut(long currentMillis) {
return (currentMillis - startMillis) > timeout;
}
}
Ok, I admit that this functionality might not be worth to write a class for, but this class is now testable very easily. You could write test cases event for threshold values or boundary value. You wouldn't want to wait 3 months for a test case that tests a three months timeout but you can now write a test case for it that runs in milliseconds.
Summary
I'm writing this not because I've learned it the hard way but because I've actually had problems similar to the currentTimeMillis
issue. Switching to nanoTime
was very easy because of the functional programming style I had used. There was basically just one line of code that acutally called that system method and that passed it as a parameters to other methods and classes. I simply hat to replace that one line and all of the other code was kept untouched.