How do you unit test classes that use timers inter

2019-02-12 11:56发布

Like it or not, occasionally you have have to write tests for classes that make internal use of timers.

Say for example a class that takes reports of system availability and raises an event if the system has been down for too long

public class SystemAvailabilityMonitor {
    public event Action SystemBecameUnavailable = delegate { };
    public event Action SystemBecameAvailable = delegate { };
    public void SystemUnavailable() {
        //..
    }
    public void SystemAvailable() {
        //..
    }
    public SystemAvailabilityMonitor(TimeSpan bufferBeforeRaisingEvent) {
        //..
    }
}

I have a couple tricks which I use (will post these as an answer) but I wonder what other people do since I'm not fully satisfied with either of my approaches.

7条回答
虎瘦雄心在
2楼-- · 2019-02-12 12:50

This is what I am using. I found it in the book: Test Driven - Practical TDD and Acceptance TDD for Java Developers by Lasse Koskela.

public interface TimeSource {
    long millis();
}


public class SystemTime {

    private static TimeSource source = null;

    private static final TimeSource DEFAULTSRC =
        new TimeSource() {
        public long millis() {
            return System.currentTimeMillis();
        }
    };


    private static TimeSource getTimeSource() {
        TimeSource answer;
        if (source == null) {
            answer = DEFAULTSRC;
        } else {
            answer = source;
        }
        return answer;
    }

    public static void setTimeSource(final TimeSource timeSource) {
        SystemTime.source = timeSource;
    }

    public static void reset() {
        setTimeSource(null);
    }

    public static long asMillis() {
        return getTimeSource().millis();
    }

    public static Date asDate() {
        return new Date(asMillis());
    }

}

Notice that the default time source, DEFAULTSRC, is System.currentTimeMillis(). It is replaced in unit tests; however, the normal behavior is the standard system time.

This is where it is used:

public class SimHengstler {

    private long lastTime = 0;

    public SimHengstler() {
        lastTime = SystemTime.asMillis();  //System.currentTimeMillis();
    }
}

And here is the unit test:

import com.company.timing.SystemTime;
import com.company.timing.TimeSource;

public class SimHengstlerTest {
    @After
    public void tearDown() {
        SystemTime.reset();
    }

    @Test
    public final void testComputeAccel() {
        // Setup
        setStartTime();
        SimHengstler instance = new SimHengstler();
        setEndTime(1020L);
    }
    private void setStartTime() {
        final long fakeStartTime = 1000L;
        SystemTime.setTimeSource(new TimeSource() {
            public long millis() {
                return fakeStartTime;
            }
        });
    }
    private void setEndTime(final long t) {
        final long fakeEndTime = t;  // 20 millisecond time difference
        SystemTime.setTimeSource(new TimeSource() {
            public long millis() {
                return fakeEndTime;
            }
        });
    }

In the unit test, I replaced the TimeSource with just a number which was set to 1000 milliseconds. That will serve as the starting time. When calling setEndTime(), I input 1020 milliseconds for the finishing time. This gave me a controlled 20 millisecond time difference.

There is no testing code in the production code, just getting the normal Systemtime.

Make sure to call reset after testing to get back to using the system time method rather than the faked time.

查看更多
登录 后发表回答