使用 Thread.sleep 进行测试

发布于 2024-08-14 04:18:58 字数 226 浏览 3 评论 0原文

使用 Thread.sleep() 来加速测试的推荐方法是什么。

我正在测试一个具有重试功能的网络库,当连接断开或发生超时错误等时。但是,该库在重试之间使用 Thread.sleep() (因此它不会连接数千个服务器重新启动时的时间)。该调用显着减慢了单元测试速度,我想知道有哪些选项可以覆盖它。

请注意,我愿意实际更改代码,或使用模拟框架来模拟 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

悲歌长辞 2024-08-21 04:18:58

将与时间相关的功能委托给单独的组件通常是一个好主意。这包括获取当前时间,以及像 Thread.sleep() 这样的延迟。这样,在测试期间就可以轻松地用模拟替换该组件,以及切换到不同的实现。

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.

想念有你 2024-08-21 04:18:58

我刚刚遇到了类似的问题,我创建了一个 Sleeper 接口来抽象这个问题:

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

默认实现使用 Thread.sleep()

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

在我的单元测试中,我注入一个 < code>FixedDateTimeAdvanceSleeper:

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

这允许我在单元测试中查询时间:

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

请注意,您需要首先使用 DateTimeUtils.setCurrentMillisFixed( new DateTime( "2014-03-26T09:37:13 " ).getMillis() ); 在测试开始时使用 DateTimeUtils.setCurrentMillisSystem(); 测试后再次恢复时间

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();

稀香 2024-08-21 04:18:58

通过设置器配置睡眠时间,并提供默认值。因此,在单元测试中,使用一个小参数(例如 1)调用 setter,然后执行调用 Thread.sleep() 的方法。

另一种类似的方法是通过布尔值进行配置,这样如果boolean设置为false<,则根本不会调用Thread.sleep() /代码>。

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.

顾铮苏瑾 2024-08-21 04:18:58

创建一些代表重试延迟策略的重试延迟类型。针对延迟调用策略类型的某种方法。随意嘲笑它。没有条件逻辑或 true/false 标志。只需注入您想要的类型即可。

在ConnectRetryPolicy.java

public interface ConnectRetryPolicy {
    void doRetryDelay();
}

中 在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);
        }
    }
}

中 在MockConnectRetryPolicy.java中

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

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
    }
}
爱的那么颓废 2024-08-21 04:18:58

我会争论你为什么要测试 Thread.sleep。看来我是你试图测试某些事件的结果的行为。

即如果发生以下情况会发生什么:

  • 连接超时
  • 连接断开

如果您基于事件对代码进行建模,那么您可以测试发生特定事件时应该发生什么,而不必提出一个屏蔽并发 API 调用的构造。否则你真正在测试什么?您是在测试应用程序对不同刺激的反应,还是只是测试 JVM 是否正常工作?

我同意其他读者的观点,有时对任何代码时间或线程相关的抽象进行抽象很有用,即虚拟时钟 http ://c2.com/cgi/wiki?VirtualClock 这样您就可以模拟任何计时/并发行为并专注于单元本身的行为。

听起来您还应该采用一种状态模式,以便您的对象根据其所处的状态具有特定的行为。即 AwaitingConnectionState、ConnectionDroppedState。转换到不同的状态将通过不同的事件,即超时、断开连接等。不确定这是否满足您的需求,但它确实删除了很多条件逻辑,这些逻辑可能使代码更加复杂和不清楚。

如果您采用这种方式,那么您仍然可以在单元级别测试行为,同时仍然可以在稍后通过集成测试或验收测试进行现场测试。

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.

i.e. 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 i.e. 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. i.e AwaitingConnectionState, ConnectionDroppedState. Transition to different states would be via the different events i.e. 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.

月亮坠入山谷 2024-08-21 04:18:58

尤金是对的,制作你自己的组件来包装你无法控制的系统我自己就这样做了,我想我会分享,这被称为“SelfShunt' 检查一下:

Generator 是一个类,当您调用 getId() 时,它会返回当前系统时间。

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;
    }
}

我们创建测试类“SelfShunt”并成为 SystemTime 组件,这样我们就可以完全控制时间。

public class BlundellSystemTime implements SystemTime {

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

我们包装不受我们控制的组件。

public interface SystemTime {

    long currentTimeMillis();

}

然后创建一个接口,以便我们的测试可以“SelfShunt”

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'

朕就是辣么酷 2024-08-21 04:18:58

对于这些类型的情况,我实现了所有休眠类都实现的默认接口:

public interface ISleep {

    /**
     * Causes the currently executing thread to sleep
     * @param milliseconds the length of time to sleep in milliseconds
     * @throws InterruptedException if any thread has interrupted the current thread
     */
    default void sleep(long milliseconds) throws InterruptedException {
        Thread.sleep(milliseconds);
    }
}

然后使用此方法而不是 Thread.sleep() 来启用测试:

class MyClass implements ISleep {

    void sleepyMethod() {
        while(stuffNotFinished) {
            doSomething();
            sleep(1000);
        }
    }

    public void doSomething() { /* ... */}

}

现在,编写快速测试非常容易,因为您可以简单地模拟对 sleep() 的调用以立即返回(使用 Mockito 的示例):

@Test void sleepyMethod_callsDoSomething() {
    var objectUnderTest = Mockito.spy(new MyClass());
    Mockito.doNothing().when(objectUnderTest).sleep(anyLong());

    objectUnderTest.sleepyMethod();

    Mockito.verify(objectUnderTest).doSomething();
}

For these kinds of cases, i implement a default interface that all sleepy classes implement:

public interface ISleep {

    /**
     * Causes the currently executing thread to sleep
     * @param milliseconds the length of time to sleep in milliseconds
     * @throws InterruptedException if any thread has interrupted the current thread
     */
    default void sleep(long milliseconds) throws InterruptedException {
        Thread.sleep(milliseconds);
    }
}

Then use this method instead of Thread.sleep() to enable testing:

class MyClass implements ISleep {

    void sleepyMethod() {
        while(stuffNotFinished) {
            doSomething();
            sleep(1000);
        }
    }

    public void doSomething() { /* ... */}

}

Now, writing fast tests is easy as pie, as you can simply mock the call to sleep() to return immediately (example using Mockito):

@Test void sleepyMethod_callsDoSomething() {
    var objectUnderTest = Mockito.spy(new MyClass());
    Mockito.doNothing().when(objectUnderTest).sleep(anyLong());

    objectUnderTest.sleepyMethod();

    Mockito.verify(objectUnderTest).doSomething();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文