简体   繁体   中英

Testing with Thread.sleep

What are the recommended approaches to using Thread.sleep() to speed up tests.

I am testing a network library with a retry functionality when connections are dropped or timeout errors occur, etc. The library however, uses a Thread.sleep() between the retries (so it won't connect thousands times while the server is restarting). The call is slowing the unit tests significantly, and I wonder what the options are to override it.

Note, I'm open to actually changing the code, or using a mocking framework to mock Thread.sleep(), but would like to hear your opinions/recommendation first.

It is usually a good idea to delegate time-related functionality to a separate component. That include getting the current time, as well as delays like Thread.sleep(). This way it is easy to substitute this component with mock during testing, as well as switch to a different implementation.

I just faced a similar issue and I created a Sleeper interface to abstract this away:

public interface Sleeper
{
    void sleep( long millis ) throws InterruptedException;
}

The default implementation uses Thread.sleep() :

public class ThreadSleeper implements Sleeper
{
    @Override
    public void sleep( long millis ) throws InterruptedException
    {
        Thread.sleep( millis );
    }
}

In my unit tests, I inject a FixedDateTimeAdvanceSleeper :

public class FixedDateTimeAdvanceSleeper implements Sleeper
{
    @Override
    public void sleep( long millis ) throws InterruptedException
    {
        DateTimeUtils.setCurrentMillisFixed( DateTime.now().getMillis() + millis );
    }
}

This allows me to query the time in a unit test:

assertThat( new DateTime( DateTimeUtils.currentTimeMillis() ) ).isEqualTo( new DateTime( "2014-03-27T00:00:30" ) );

Note that you need to fix the time first using DateTimeUtils.setCurrentMillisFixed( new DateTime( "2014-03-26T09:37:13" ).getMillis() ); at the start of your test and restore the time again after the test using DateTimeUtils.setCurrentMillisSystem();

Make the sleeping time configurable through a setter, and provide a default value. So in your unit tests, call the setter with a small argument (1 for example), and then execute the method that would call Thread.sleep() .

Another similar approach is to make if configurable via a boolean, so that Thread.sleep() isn't called at all if the boolean is set to false .

I would argue why are you trying to test Thread.sleep. It seems to be me you're trying to test the behaviour as a consequence of some event.

ie what happens if:

  • connection timeout
  • connection dropped

If you model code based on events then you can test what should happen should a particular event occurred rather than having to come up with a construct that masks the concurrent API calls. Else what are you really testing? Are you testing how your application reacts to different stimuli or simply testing the JVM is working correctly?

I agree with the other readers that sometimes it's useful to put an abstraction around any code time or thread related ie Virtual clock http://c2.com/cgi/wiki?VirtualClock so you can mock out any timing/concurrent behaviour and concentrate on the behaviour of the unit itself.

It also sounds like you should adopt a state pattern so you object has specific behaviour depending on what state it's in. ie AwaitingConnectionState, ConnectionDroppedState. Transition to different states would be via the different events ie timeout, dropped connection etc. Not sure if this overkill for your needs but it certainly removes a lot of conditional logic which can make the code more complicated and unclear.

If you approach this way, then you can still test behaviour at the unit level whilst still testing in situ with an integration test or acceptance test later.

Create some retry delay type that represents the policy for retry delays. Invoke some method on the policy type for the delay. Mock it as you like. No conditional logic, or true / false flags. Just inject the type that you want.

In ConnectRetryPolicy.java

public interface ConnectRetryPolicy {
    void doRetryDelay();
}

In SleepConnectRetryPolicy.java

public class final SleepConnectRetryPolicy implements ConnectRetryPolicy {
    private final int delay;
    public SleepConnectRetryPolicy(final int delay) {
        this.delay = delay;
    }

    @Override
    public void doRetryDelay() {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException ie) {
            log.error("connection delay sleep interrupted", ie);
        }
    }
}

In MockConnectRetryPolicy.java

public final class MockConnectRetryPolicy implements ConnectRetryPolicy {    
    @Override
    public void doRetryDelay() {
        // no delay
    }
}

Eugene is right, make your own component to wrap the system that is out of your control Just done this myself thought I'd share, this is known as ' SelfShunt ' check this out:

Generator is a class that when you call getId() it returns the current system time.

public class GeneratorTests implements SystemTime {

    private Generator cut;
    private long currentSystemTime;

    @Before
    public void setup(){
        cut = Generator.getInstance(this);
    }

    @Test
    public void testGetId_returnedUniqueId(){
        currentSystemTime = 123;

        String id = cut.getId();

        assertTrue(id.equals("123"));
    }

    @Override
    public long currentTimeMillis() {
        return currentSystemTime;
    }
}

We make the test class 'SelfShunt' and become the SystemTime component that way we have full control of what the time is.

public class BlundellSystemTime implements SystemTime {

    @Override
    public long currentTimeMillis(){
        return System.currentTimeMillis();
    }
}

We wrap the component that isn't under our control.

public interface SystemTime {

    long currentTimeMillis();

}

Then make an interface so our test can 'SelfShunt'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM